testss
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VSProjectSettingsProvider : Editor
|
||||
{
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateProjectSettingProvider()
|
||||
{
|
||||
return new VSProjectSettingsProviderView();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
class VSProjectSettingsProviderView : SettingsProvider
|
||||
{
|
||||
private const string path = "Project/Visual Scripting";
|
||||
private const string title = "Visual Scripting";
|
||||
private const string titleGroup = "Generate Units";
|
||||
|
||||
VSSettingsAssembly vsSettingsAssembly;
|
||||
VSSettingsTypeOption vsSettingsTypeOption;
|
||||
VSSettingsCustomProperty vsSettingsCustomProperty;
|
||||
VSSettingsBackup vsSettingsBackup;
|
||||
VSSettingsScriptReferenceResolver vsSettingsScriptReferenceResolver;
|
||||
|
||||
public VSProjectSettingsProviderView() : base(path, SettingsScope.Project)
|
||||
{
|
||||
label = title;
|
||||
}
|
||||
|
||||
private void CreateOptionsIfNeeded()
|
||||
{
|
||||
if (vsSettingsAssembly == null)
|
||||
{
|
||||
vsSettingsAssembly = new VSSettingsAssembly();
|
||||
}
|
||||
|
||||
if (vsSettingsTypeOption == null)
|
||||
{
|
||||
vsSettingsTypeOption = new VSSettingsTypeOption();
|
||||
}
|
||||
|
||||
if (vsSettingsCustomProperty == null)
|
||||
{
|
||||
vsSettingsCustomProperty = new VSSettingsCustomProperty();
|
||||
}
|
||||
|
||||
if (vsSettingsBackup == null)
|
||||
{
|
||||
vsSettingsBackup = new VSSettingsBackup();
|
||||
}
|
||||
|
||||
if (vsSettingsScriptReferenceResolver == null)
|
||||
{
|
||||
vsSettingsScriptReferenceResolver = new VSSettingsScriptReferenceResolver();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGUI(string searchContext)
|
||||
{
|
||||
GUILayout.Space(5f);
|
||||
|
||||
GUILayout.Label(titleGroup, EditorStyles.boldLabel);
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
// happens when opening unity with the settings window already opened. there's a delay until the singleton is assigned
|
||||
if (BoltCore.instance == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("Loading Configuration...", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
CreateOptionsIfNeeded();
|
||||
|
||||
vsSettingsTypeOption.OnGUI();
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
vsSettingsAssembly.OnGUI();
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
vsSettingsCustomProperty.OnGUI();
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
vsSettingsBackup.OnGUI();
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
vsSettingsScriptReferenceResolver.OnGUI();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VSSettingsAssembly
|
||||
{
|
||||
private const string completeLabel = "Regenerate Units";
|
||||
private PluginConfigurationItemMetadata assemblyOptionsMetadata;
|
||||
|
||||
private bool showAssembly = false;
|
||||
private const string titleAssembly = "Node Library";
|
||||
private const string descriptionAssembly = "Choose the assemblies in which you want to look for units.\n"
|
||||
+ "By default, all project and Unity assemblies are included.\n"
|
||||
+ "Unless you use a third-party plugin distributed as a DLL, you shouldn't need to change this.";
|
||||
public VSSettingsAssembly()
|
||||
{
|
||||
assemblyOptionsMetadata = BoltCore.Configuration.GetMetadata(nameof(BoltCoreConfiguration.assemblyOptions));
|
||||
}
|
||||
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIStyle background;
|
||||
public static readonly GUIStyle defaultsButton;
|
||||
public static readonly float optionsWidth = 250;
|
||||
|
||||
static Styles()
|
||||
{
|
||||
background = new GUIStyle(LudiqStyles.windowBackground);
|
||||
background.padding = new RectOffset(20, 20, 20, 20);
|
||||
|
||||
defaultsButton = new GUIStyle("Button");
|
||||
defaultsButton.padding = new RectOffset(10, 10, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
showAssembly = EditorGUILayout.Foldout(showAssembly, new GUIContent(titleAssembly, descriptionAssembly));
|
||||
|
||||
if (showAssembly)
|
||||
{
|
||||
GUILayout.BeginVertical(Styles.background, GUILayout.ExpandHeight(true));
|
||||
|
||||
float height = LudiqGUI.GetInspectorHeight(null, assemblyOptionsMetadata, Styles.optionsWidth, GUIContent.none);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var position = GUILayoutUtility.GetRect(Styles.optionsWidth, height);
|
||||
|
||||
LudiqGUI.Inspector(assemblyOptionsMetadata, position, GUIContent.none);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
assemblyOptionsMetadata.Save();
|
||||
Codebase.UpdateSettings();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Reset to Defaults", Styles.defaultsButton))
|
||||
{
|
||||
assemblyOptionsMetadata.Reset(true);
|
||||
assemblyOptionsMetadata.Save();
|
||||
}
|
||||
|
||||
LudiqGUI.EndVertical();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(completeLabel, Styles.defaultsButton))
|
||||
{
|
||||
UnitBase.Rebuild();
|
||||
|
||||
EditorUtility.DisplayDialog("Visual Script", "Regenerate Units completed", "OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System.Diagnostics;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VSSettingsBackup
|
||||
{
|
||||
private const string title = "Backup Graphs";
|
||||
private const string buttonBackupLabel = "Create Backup";
|
||||
private const string buttonRestoreLabel = "Restore Backup";
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
GUILayout.Space(5f);
|
||||
|
||||
GUILayout.Label(title, EditorStyles.boldLabel);
|
||||
|
||||
GUILayout.Space(5f);
|
||||
|
||||
if (GUILayout.Button(buttonBackupLabel, Styles.defaultsButton))
|
||||
{
|
||||
VSBackupUtility.Backup();
|
||||
|
||||
EditorUtility.DisplayDialog("Backup", "Backup completed successfully.", "OK");
|
||||
}
|
||||
|
||||
if (GUILayout.Button(buttonRestoreLabel, Styles.defaultsButton))
|
||||
{
|
||||
PathUtility.CreateDirectoryIfNeeded(Paths.backups);
|
||||
Process.Start(Paths.backups);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Styles
|
||||
{
|
||||
static Styles()
|
||||
{
|
||||
defaultsButton = new GUIStyle("Button");
|
||||
defaultsButton.padding = new RectOffset(10, 10, 4, 4);
|
||||
}
|
||||
|
||||
public static readonly GUIStyle defaultsButton;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VSSettingsCustomProperty
|
||||
{
|
||||
private const string title = "Custom Inspector Properties";
|
||||
private const string buttonLabel = "Generate";
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
GUILayout.Space(5f);
|
||||
|
||||
GUILayout.Label(title, EditorStyles.boldLabel);
|
||||
|
||||
GUILayout.Space(5f);
|
||||
|
||||
string label = "Inspectors in Bolt plugins can handle many custom types besides Unity primites and objects. ";
|
||||
label += "However, to be compatible with your custom editor drawers, some additional property provider scripts must be generated. ";
|
||||
|
||||
GUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
GUILayout.Label(EditorGUIUtility.IconContent("console.infoicon"), GUILayout.ExpandWidth(true));
|
||||
GUILayout.Box(label, EditorStyles.wordWrappedLabel);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button(buttonLabel, Styles.defaultsButton))
|
||||
{
|
||||
SerializedPropertyProviderProvider.instance.GenerateProviderScripts();
|
||||
EditorUtility.DisplayDialog("Custom Inspector Generation", "Custom inspector generation has completed successfully.", "OK");
|
||||
}
|
||||
}
|
||||
|
||||
public static class Styles
|
||||
{
|
||||
static Styles()
|
||||
{
|
||||
defaultsButton = new GUIStyle("Button");
|
||||
defaultsButton.padding = new RectOffset(10, 10, 4, 4);
|
||||
|
||||
regenerateLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
|
||||
regenerateLabel.wordWrap = true;
|
||||
}
|
||||
|
||||
public static readonly GUIStyle defaultsButton;
|
||||
public static readonly GUIStyle regenerateLabel;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "VSSettingsProvider",
|
||||
"references": [
|
||||
"Unity.VisualScripting.Core",
|
||||
"Unity.VisualScripting.Core.Editor",
|
||||
"Unity.VisualScripting.Flow",
|
||||
"Unity.VisualScripting.Flow.Editor",
|
||||
"Unity.VisualScripting.State"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VSSettingsScriptReferenceResolver
|
||||
{
|
||||
private const string title = "Script Reference Resolver";
|
||||
private const string buttonLabel = "Fix Missing Scripts";
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
GUILayout.Space(5f);
|
||||
|
||||
GUILayout.Label(title, EditorStyles.boldLabel);
|
||||
|
||||
GUILayout.Space(5f);
|
||||
|
||||
if (GUILayout.Button(buttonLabel, Styles.defaultsButton))
|
||||
{
|
||||
ScriptReferenceResolver.Run(ScriptReferenceResolver.Mode.Dialog);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Styles
|
||||
{
|
||||
static Styles()
|
||||
{
|
||||
defaultsButton = new GUIStyle("Button");
|
||||
defaultsButton.padding = new RectOffset(10, 10, 4, 4);
|
||||
|
||||
regenerateLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
|
||||
regenerateLabel.wordWrap = true;
|
||||
}
|
||||
|
||||
public static readonly GUIStyle defaultsButton;
|
||||
public static readonly GUIStyle regenerateLabel;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VSSettingsTypeOption
|
||||
{
|
||||
private readonly PluginConfigurationItemMetadata typeOptionsMetadata;
|
||||
|
||||
bool showTypeOption = false;
|
||||
private const string titleTypeOption = "Type Options";
|
||||
private const string descriptionTypeOption = "Choose the types you want to use for variables and units.\n"
|
||||
+ "MonoBehaviour types are always included.";
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIStyle background;
|
||||
public static readonly GUIStyle defaultsButton;
|
||||
public static readonly float optionsWidth = 250;
|
||||
|
||||
static Styles()
|
||||
{
|
||||
background = new GUIStyle(LudiqStyles.windowBackground);
|
||||
background.padding = new RectOffset(20, 20, 20, 20);
|
||||
|
||||
defaultsButton = new GUIStyle("Button");
|
||||
defaultsButton.padding = new RectOffset(10, 10, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
public VSSettingsTypeOption()
|
||||
{
|
||||
typeOptionsMetadata = BoltCore.Configuration.GetMetadata(nameof(BoltCoreConfiguration.typeOptions));
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
showTypeOption = EditorGUILayout.Foldout(showTypeOption, new GUIContent(titleTypeOption, descriptionTypeOption));
|
||||
|
||||
if (showTypeOption)
|
||||
{
|
||||
GUILayout.BeginVertical(Styles.background, GUILayout.ExpandHeight(true));
|
||||
|
||||
float height =
|
||||
LudiqGUI.GetInspectorHeight(null, typeOptionsMetadata, Styles.optionsWidth, GUIContent.none);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var position = GUILayoutUtility.GetRect(Styles.optionsWidth, height);
|
||||
|
||||
LudiqGUI.Inspector(typeOptionsMetadata, position, GUIContent.none);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
typeOptionsMetadata.Save();
|
||||
Codebase.UpdateSettings();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Reset to Defaults", Styles.defaultsButton))
|
||||
{
|
||||
typeOptionsMetadata.Reset(true);
|
||||
typeOptionsMetadata.Save();
|
||||
}
|
||||
|
||||
LudiqGUI.EndVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Analyser<TTarget, TAnalysis> : Assigner<TTarget, TAnalysis>, IAnalyser
|
||||
where TAnalysis : class, IAnalysis, new()
|
||||
{
|
||||
protected Analyser(GraphReference reference, TTarget target) : base(target, new TAnalysis())
|
||||
{
|
||||
Ensure.That(nameof(reference)).IsNotNull(reference);
|
||||
|
||||
this.reference = reference;
|
||||
|
||||
// HACK: It makes more sense to think of analysis as reference-bound,
|
||||
// however in practice they are context-bound and therefore it is safe
|
||||
// (and more importantly faster) to cache the context once for recursive
|
||||
// analyses.
|
||||
this.context = reference.Context();
|
||||
}
|
||||
|
||||
public TAnalysis analysis => assignee;
|
||||
|
||||
IAnalysis IAnalyser.analysis => analysis;
|
||||
|
||||
protected IGraphContext context { get; }
|
||||
|
||||
public GraphReference reference { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class AnalyserAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public AnalyserAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class AnalyserProvider : SingleDecoratorProvider<object, IAnalyser, AnalyserAttribute>
|
||||
{
|
||||
protected override bool cache => true;
|
||||
|
||||
public GraphReference reference { get; }
|
||||
|
||||
public AnalyserProvider(GraphReference reference)
|
||||
{
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
protected override IAnalyser CreateDecorator(Type decoratorType, object decorated)
|
||||
{
|
||||
return (IAnalyser)decoratorType.Instantiate(true, reference, decorated);
|
||||
}
|
||||
|
||||
public override bool IsValid(object analyzed)
|
||||
{
|
||||
return !analyzed.IsUnityNull();
|
||||
}
|
||||
|
||||
public void Analyze(object analyzed)
|
||||
{
|
||||
GetDecorator(analyzed).isDirty = true;
|
||||
}
|
||||
|
||||
public void AnalyzeAll()
|
||||
{
|
||||
foreach (var analyser in decorators.Values)
|
||||
{
|
||||
analyser.isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class XAnalyserProvider
|
||||
{
|
||||
// Analysis are conceptually reference-bound, but practically context-bound,
|
||||
// so it's faster to avoid the reference-to-context lookup if we can avoid it.
|
||||
|
||||
public static IAnalyser Analyser(this object target, IGraphContext context)
|
||||
{
|
||||
return context.analyserProvider.GetDecorator(target);
|
||||
}
|
||||
|
||||
public static TAnalyser Analyser<TAnalyser>(this object target, IGraphContext context) where TAnalyser : IAnalyser
|
||||
{
|
||||
return context.analyserProvider.GetDecorator<TAnalyser>(target);
|
||||
}
|
||||
|
||||
public static IAnalysis Analysis(this object target, IGraphContext context)
|
||||
{
|
||||
var analyser = target.Analyser(context);
|
||||
analyser.Validate();
|
||||
return analyser.analysis;
|
||||
}
|
||||
|
||||
public static TAnalysis Analysis<TAnalysis>(this object target, IGraphContext context) where TAnalysis : IAnalysis
|
||||
{
|
||||
return (TAnalysis)target.Analysis(context);
|
||||
}
|
||||
|
||||
// Shortcuts, but the above are faster because Context doesn't have to be looked up
|
||||
|
||||
public static IAnalyser Analyser(this object target, GraphReference reference)
|
||||
{
|
||||
return target.Analyser(reference.Context());
|
||||
}
|
||||
|
||||
public static TAnalyser Analyser<TAnalyser>(this object target, GraphReference reference) where TAnalyser : IAnalyser
|
||||
{
|
||||
return target.Analyser<TAnalyser>(reference.Context());
|
||||
}
|
||||
|
||||
public static IAnalysis Analysis(this object target, GraphReference reference)
|
||||
{
|
||||
return target.Analysis(reference.Context());
|
||||
}
|
||||
|
||||
public static TAnalysis Analysis<TAnalysis>(this object target, GraphReference reference) where TAnalysis : IAnalysis
|
||||
{
|
||||
return target.Analysis<TAnalysis>(reference.Context());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Analysis : IAnalysis
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphElementAnalysis : IGraphElementAnalysis
|
||||
{
|
||||
public List<Warning> warnings { get; set; } = new List<Warning>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IAnalyser
|
||||
{
|
||||
IAnalysis analysis { get; }
|
||||
|
||||
bool isDirty { get; set; }
|
||||
|
||||
void Validate();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IAnalysis
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphElementAnalysis : IAnalysis
|
||||
{
|
||||
List<Warning> warnings { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class UsageAnalytics
|
||||
{
|
||||
const int k_MaxEventsPerHour = 1000;
|
||||
const int k_MaxNumberOfElements = 1000;
|
||||
const string k_VendorKey = "unity.bolt";
|
||||
const string k_EventName = "BoltUsage";
|
||||
static bool isRegistered = false;
|
||||
|
||||
public static void CollectAndSend()
|
||||
{
|
||||
if (!EditorAnalytics.enabled)
|
||||
return;
|
||||
|
||||
if (!RegisterEvent())
|
||||
return;
|
||||
|
||||
var data = CollectData();
|
||||
|
||||
EditorAnalytics.SendEventWithLimit(k_EventName, data);
|
||||
}
|
||||
|
||||
private static bool RegisterEvent()
|
||||
{
|
||||
if (!isRegistered)
|
||||
{
|
||||
var result = EditorAnalytics.RegisterEventWithLimit(k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey);
|
||||
if (result == UnityEngine.Analytics.AnalyticsResult.Ok)
|
||||
{
|
||||
isRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isRegistered;
|
||||
}
|
||||
|
||||
private static UsageAnalyticsData CollectData()
|
||||
{
|
||||
var data = new UsageAnalyticsData
|
||||
{
|
||||
productVersion = BoltProduct.instance.version.ToString(),
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private struct UsageAnalyticsData
|
||||
{
|
||||
public string productVersion;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Assigner<TTarget, TAssignee> : IAssigner
|
||||
where TAssignee : class
|
||||
{
|
||||
protected Assigner(TTarget target, TAssignee assignee)
|
||||
{
|
||||
Ensure.That(nameof(target)).IsNotNull(target);
|
||||
Ensure.That(nameof(assignee)).IsNotNull(assignee);
|
||||
|
||||
this.target = target;
|
||||
this.assignee = assignee;
|
||||
|
||||
var assignerType = GetType();
|
||||
|
||||
if (!_assignments.ContainsKey(assignerType))
|
||||
{
|
||||
_assignments.Add(assignerType, Assignment.Fetch(assignerType, typeof(TAssignee)).ToArray());
|
||||
_transientAssignments.Add(assignerType, _assignments[assignerType].Where(a => !a.cache).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public TTarget target { get; }
|
||||
|
||||
public TAssignee assignee { get; }
|
||||
|
||||
public bool isDirty { get; set; } = true;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (isDirty)
|
||||
{
|
||||
AssignAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
AssignTransient();
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssignAll()
|
||||
{
|
||||
isDirty = false;
|
||||
|
||||
foreach (var assignment in assignments)
|
||||
{
|
||||
assignment.Run(this, assignee);
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssignTransient()
|
||||
{
|
||||
foreach (var assignment in transientAssignments)
|
||||
{
|
||||
assignment.Run(this, assignee);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ValueChanged() { }
|
||||
|
||||
public Assignment[] assignments => _assignments[GetType()];
|
||||
public Assignment[] transientAssignments => _transientAssignments[GetType()];
|
||||
|
||||
private static readonly Dictionary<Type, Assignment[]> _assignments = new Dictionary<Type, Assignment[]>();
|
||||
|
||||
private static readonly Dictionary<Type, Assignment[]> _transientAssignments = new Dictionary<Type, Assignment[]>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class Assignment
|
||||
{
|
||||
public Assignment(Member assigner, Type assigneeType)
|
||||
{
|
||||
Ensure.That(nameof(assigneeType)).IsNotNull(assigneeType);
|
||||
|
||||
this.assigner = assigner;
|
||||
|
||||
var assignsAttribute = assigner.info.GetAttribute<AssignsAttribute>();
|
||||
assignee = new Member(assigneeType, assignsAttribute?.memberName ?? assigner.name.FirstCharacterToLower());
|
||||
requiresAPI = assigner.info.HasAttribute<RequiresUnityAPIAttribute>();
|
||||
cache = assignsAttribute?.cache ?? true;
|
||||
|
||||
assigner.Prewarm();
|
||||
assignee.Prewarm();
|
||||
}
|
||||
|
||||
public Member assigner { get; }
|
||||
public Member assignee { get; }
|
||||
public bool requiresAPI { get; }
|
||||
public bool cache { get; }
|
||||
|
||||
public void Run(object assigner, object assignee)
|
||||
{
|
||||
if (requiresAPI)
|
||||
{
|
||||
UnityAPI.Async(() => _Run(assigner, assignee));
|
||||
}
|
||||
else
|
||||
{
|
||||
_Run(assigner, assignee);
|
||||
}
|
||||
}
|
||||
|
||||
private void _Run(object assigner, object assignee)
|
||||
{
|
||||
var oldValue = this.assignee.Get(assignee);
|
||||
var newValue = ConversionUtility.Convert(this.assigner.Invoke(assigner), this.assignee.type);
|
||||
|
||||
this.assignee.Set(assignee, newValue);
|
||||
|
||||
if (!Equals(oldValue, newValue))
|
||||
{
|
||||
if (assigner is IAssigner _assigner)
|
||||
{
|
||||
_assigner.ValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Assignment> Fetch(Type descriptorType, Type descriptionType)
|
||||
{
|
||||
var bindingFlags = BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic;
|
||||
|
||||
return descriptorType.GetMethods(bindingFlags)
|
||||
.Where(m => m.HasAttribute<AssignsAttribute>())
|
||||
.Select(m => new Assignment(m.ToManipulator(descriptorType), descriptionType));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class AssignsAttribute : Attribute
|
||||
{
|
||||
public AssignsAttribute() { }
|
||||
|
||||
public AssignsAttribute(string memberName)
|
||||
{
|
||||
this.memberName = memberName;
|
||||
}
|
||||
|
||||
public string memberName { get; }
|
||||
|
||||
public bool cache { get; set; } = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IAssigner
|
||||
{
|
||||
void ValueChanged();
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEvent = UnityEngine.Event;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class BoltGUI
|
||||
{
|
||||
private static UnityEvent e => UnityEvent.current;
|
||||
|
||||
public static float GetVariableFieldHeight(GUIContent label, string value, ActionDirection direction)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
|
||||
public static string VariableField(Rect position, GUIContent label, string value, ActionDirection direction)
|
||||
{
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
var style = direction != ActionDirection.Any ? BoltStyles.variableFieldWithDirectionIndicator : BoltStyles.variableFieldWithoutDirectionIndicator;
|
||||
value = EditorGUI.TextField(position, GUIContent.none, value, style);
|
||||
|
||||
var iconPosition = new Rect
|
||||
(
|
||||
position.x + 3,
|
||||
position.y + 2,
|
||||
12,
|
||||
12
|
||||
);
|
||||
|
||||
GUI.DrawTexture(iconPosition, BoltCore.Icons.variable?[IconSize.Small]);
|
||||
|
||||
if (direction != ActionDirection.Any)
|
||||
{
|
||||
var arrowPosition = new Rect
|
||||
(
|
||||
iconPosition.xMax + (direction == ActionDirection.Get ? 12 : -2),
|
||||
position.y + 2,
|
||||
12 * (direction == ActionDirection.Get ? -1 : 1),
|
||||
12
|
||||
);
|
||||
|
||||
if (e.type == EventType.Repaint)
|
||||
{
|
||||
BoltStyles.variableFieldDirectionIndicator.Draw(arrowPosition, false, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Product(ID)]
|
||||
public sealed class BoltProduct : Product
|
||||
{
|
||||
public BoltProduct() { }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
logo = BoltCore.Resources.LoadTexture("Logos/LogoBolt.png", CreateTextureOptions.Scalable)?.Single();
|
||||
}
|
||||
|
||||
public override string configurationPanelLabel => "Bolt";
|
||||
|
||||
public override string name => "Bolt";
|
||||
public override string description => "";
|
||||
public override string authorLabel => "Designed & Developed by ";
|
||||
public override string author => "";
|
||||
public override string copyrightHolder => "Unity";
|
||||
public override SemanticVersion version => "1.5.1";
|
||||
public override string publisherUrl => "";
|
||||
public override string websiteUrl => "";
|
||||
public override string supportUrl => "";
|
||||
public override string manualUrl => "https://docs.unity3d.com/Packages/com.unity.bolt@latest";
|
||||
public override string assetStoreUrl => "http://u3d.as/1Md2";
|
||||
|
||||
public const string ID = "Bolt";
|
||||
|
||||
#if VISUAL_SCRIPT_INTERNAL
|
||||
public const int ToolsMenuPriority = -990000;
|
||||
public const int DeveloperToolsMenuPriority = ToolsMenuPriority + 1000;
|
||||
#endif
|
||||
|
||||
public static BoltProduct instance => (BoltProduct)ProductContainer.GetProduct(ID);
|
||||
|
||||
[SettingsProvider]
|
||||
private static SettingsProvider BoltSettingsProvider()
|
||||
{
|
||||
return new VSEditorSettingsProviderView();
|
||||
}
|
||||
|
||||
private static bool PrepareForRelease()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("Delete Generated Files", "This action will delete all generated files, including those containing user data.\n\nAre you sure you want to continue?", "Confirm", "Cancel"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginConfiguration.DeleteAllProjectSettings();
|
||||
|
||||
foreach (var plugin in PluginContainer.plugins)
|
||||
{
|
||||
PathUtility.DeleteDirectoryIfExists(plugin.paths.persistentGenerated);
|
||||
PathUtility.DeleteDirectoryIfExists(plugin.paths.transientGenerated);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if VISUAL_SCRIPT_INTERNAL
|
||||
[MenuItem("Tools/Bolt/Internal/Export Release Asset Package...", priority = DeveloperToolsMenuPriority + 102)]
|
||||
#endif
|
||||
private static void ExportReleasePackage()
|
||||
{
|
||||
if (!PrepareForRelease())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var exportPath = EditorUtility.SaveFilePanel("Export Release Package",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
"Bolt_" + instance.version.ToString().Replace(".", "_").Replace(" ", "_"),
|
||||
"unitypackage");
|
||||
|
||||
if (exportPath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var packageDirectory = Path.GetDirectoryName(exportPath);
|
||||
|
||||
var paths = new List<string>()
|
||||
{
|
||||
PathUtility.GetPackageRootPath()
|
||||
};
|
||||
|
||||
AssetDatabase.ExportPackage(paths.ToArray(), exportPath, ExportPackageOptions.Recurse);
|
||||
|
||||
if (EditorUtility.DisplayDialog("Export Release Package", "Release package export complete.\nOpen containing folder?", "Open Folder", "Close"))
|
||||
{
|
||||
Process.Start(packageDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class BoltStyles
|
||||
{
|
||||
static BoltStyles()
|
||||
{
|
||||
// Variables
|
||||
|
||||
variableFieldDirectionIndicator = "AC LeftArrow";
|
||||
variableFieldWithoutDirectionIndicator = new GUIStyle(EditorStyles.textField);
|
||||
variableFieldWithoutDirectionIndicator.padding.left = IconSize.Small;
|
||||
variableFieldWithDirectionIndicator = new GUIStyle(variableFieldWithoutDirectionIndicator);
|
||||
variableFieldWithDirectionIndicator.padding.left = 24;
|
||||
}
|
||||
|
||||
// Variables
|
||||
public static readonly GUIStyle variableFieldDirectionIndicator;
|
||||
public static readonly GUIStyle variableFieldWithDirectionIndicator;
|
||||
public static readonly GUIStyle variableFieldWithoutDirectionIndicator;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public enum AlignOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Align the left edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignLeftEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Align the horizontal centers of the selected elements.
|
||||
/// </summary>
|
||||
AlignCenters,
|
||||
|
||||
/// <summary>
|
||||
/// Align the right edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignRightEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Align the top edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignTopEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Align the vertical middles of the selected elements.
|
||||
/// </summary>
|
||||
AlignMiddles,
|
||||
|
||||
/// <summary>
|
||||
/// Align the bottom edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignBottomEdges
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class CanvasAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public CanvasAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public enum CanvasControlScheme
|
||||
{
|
||||
Unity,
|
||||
Unreal
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class CanvasProvider : SingleDecoratorProvider<IGraph, ICanvas, CanvasAttribute>
|
||||
{
|
||||
static CanvasProvider()
|
||||
{
|
||||
instance = new CanvasProvider();
|
||||
}
|
||||
|
||||
public static CanvasProvider instance { get; }
|
||||
|
||||
protected override bool cache => true;
|
||||
|
||||
public override bool IsValid(IGraph graph)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class XCanvasProvider
|
||||
{
|
||||
public static ICanvas Canvas(this IGraph graph)
|
||||
{
|
||||
return CanvasProvider.instance.GetDecorator(graph);
|
||||
}
|
||||
|
||||
public static TCanvas Canvas<TCanvas>(this IGraph graph) where TCanvas : ICanvas
|
||||
{
|
||||
return CanvasProvider.instance.GetDecorator<TCanvas>(graph);
|
||||
}
|
||||
|
||||
public static IWidget Widget(this ICanvas context, IGraphItem item)
|
||||
{
|
||||
return context.widgetProvider.GetDecorator(item);
|
||||
}
|
||||
|
||||
public static TWidget Widget<TWidget>(this ICanvas context, IGraphItem item) where TWidget : IWidget
|
||||
{
|
||||
return context.widgetProvider.GetDecorator<TWidget>(item);
|
||||
}
|
||||
|
||||
public static IGraphElementWidget Widget(this ICanvas context, IGraphElement element)
|
||||
{
|
||||
return (IGraphElementWidget)context.widgetProvider.GetDecorator(element);
|
||||
}
|
||||
|
||||
public static TWidget Widget<TWidget>(this ICanvas context, IGraphElement element) where TWidget : IGraphElementWidget
|
||||
{
|
||||
return context.widgetProvider.GetDecorator<TWidget>(element);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public enum DistributeOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the left edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeLeftEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the horizontal centers
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeCenters,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the right edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeRightEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the horizontal gaps
|
||||
/// are all of equal size.
|
||||
/// </summary>
|
||||
DistributeHorizontalGaps,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the top edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeTopEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the vertical middles
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeMiddles,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the bottom edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeBottomEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the vertical gaps
|
||||
/// are all of equal size.
|
||||
/// </summary>
|
||||
DistributeVerticalGaps
|
||||
}
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface ICanvas : IDisposable, IDragAndDropHandler
|
||||
{
|
||||
void Cache();
|
||||
|
||||
#region Model
|
||||
|
||||
ICanvasWindow window { get; set; }
|
||||
|
||||
IGraph graph { get; }
|
||||
|
||||
WidgetProvider widgetProvider { get; }
|
||||
|
||||
GraphSelection selection { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Widgets
|
||||
|
||||
IEnumerable<IWidget> widgets { get; }
|
||||
|
||||
void Recollect();
|
||||
|
||||
void CacheWidgetCollections();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Hovering
|
||||
|
||||
IWidget hoveredWidget { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Deleting
|
||||
|
||||
void DeleteSelection();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Clipboard
|
||||
|
||||
void ShrinkCopyGroup(HashSet<IGraphElement> copyGroup);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
void RegisterControls();
|
||||
|
||||
void Open();
|
||||
|
||||
void Close();
|
||||
|
||||
void Update();
|
||||
|
||||
void BeforeFrame();
|
||||
|
||||
void OnGUI();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Viewing
|
||||
|
||||
float zoom { get; }
|
||||
|
||||
Vector2 pan { get; }
|
||||
|
||||
void UpdateViewport();
|
||||
|
||||
Rect viewport { get; set; }
|
||||
|
||||
Vector2 mousePosition { get; }
|
||||
|
||||
bool isMouseOver { get; }
|
||||
|
||||
bool isMouseOverBackground { get; }
|
||||
|
||||
void ViewElements(IEnumerable<IGraphElement> elements);
|
||||
|
||||
bool IsVisible(IWidget widget);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Positioning
|
||||
|
||||
void CacheWidgetPositions();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Lassoing
|
||||
|
||||
bool isLassoing { get; }
|
||||
|
||||
Rect lassoArea { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Selecting
|
||||
|
||||
bool isSelecting { get; }
|
||||
|
||||
Rect selectionArea { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Grouping
|
||||
|
||||
bool isGrouping { get; }
|
||||
|
||||
Rect groupArea { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Dragging
|
||||
|
||||
bool isDragging { get; }
|
||||
|
||||
void BeginDrag(EventWrapper e);
|
||||
|
||||
void Drag(EventWrapper e);
|
||||
|
||||
void EndDrag(EventWrapper e);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Layout
|
||||
|
||||
void Align(AlignOperation operation);
|
||||
|
||||
void Distribute(DistributeOperation operation);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Timing
|
||||
|
||||
float frameDeltaTime { get; }
|
||||
|
||||
float eventDeltaTime { get; }
|
||||
|
||||
float repaintDeltaTime { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Window
|
||||
|
||||
void OnToolbarGUI();
|
||||
|
||||
event Action delayCall;
|
||||
|
||||
Queue<Action> delayedCalls { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphContextExtension : IDragAndDropHandler
|
||||
{
|
||||
IEnumerable<GraphContextMenuItem> contextMenuItems { get; }
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of widgets that can be safely iterated over even if the collection changes during iteration.
|
||||
/// </summary>
|
||||
public class WidgetList<TWidget> : Collection<TWidget>, IEnumerable<TWidget> where TWidget : class, IWidget
|
||||
{
|
||||
private uint version;
|
||||
|
||||
private readonly ICanvas canvas;
|
||||
|
||||
public WidgetList(ICanvas canvas)
|
||||
{
|
||||
Ensure.That(nameof(canvas)).IsNotNull(canvas);
|
||||
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, TWidget item)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
version++;
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
base.RemoveItem(index);
|
||||
version++;
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
base.ClearItems();
|
||||
version++;
|
||||
}
|
||||
|
||||
public new Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<TWidget> IEnumerable<TWidget>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<TWidget>
|
||||
{
|
||||
private readonly WidgetList<TWidget> list;
|
||||
|
||||
private int index;
|
||||
|
||||
private TWidget current;
|
||||
|
||||
private bool invalid;
|
||||
|
||||
private readonly uint version;
|
||||
|
||||
private IGraph graph;
|
||||
|
||||
public Enumerator(WidgetList<TWidget> list) : this()
|
||||
{
|
||||
this.list = list;
|
||||
version = list.version;
|
||||
|
||||
// Micro optim
|
||||
graph = list.canvas.graph;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
// Stop if the list changed
|
||||
if (version != list.version)
|
||||
{
|
||||
current = null;
|
||||
invalid = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Micro optim
|
||||
var count = list.Count;
|
||||
|
||||
// Find the next element that is within the graph
|
||||
while (index < count)
|
||||
{
|
||||
current = list[index];
|
||||
index++;
|
||||
|
||||
if (current.item.graph == graph)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Expleted the list
|
||||
current = null;
|
||||
invalid = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public TWidget Current => current;
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (invalid)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[InitializeAfterPlugins]
|
||||
public static class GraphClipboard
|
||||
{
|
||||
static GraphClipboard()
|
||||
{
|
||||
singleClipboard = new Clipboard();
|
||||
groupClipboard = new Clipboard();
|
||||
|
||||
GraphWindow.activeContextChanged += OnContextChange;
|
||||
}
|
||||
|
||||
private static Event e => Event.current;
|
||||
|
||||
private static void OnContextChange(IGraphContext context)
|
||||
{
|
||||
GraphClipboard.context = context;
|
||||
}
|
||||
|
||||
private static IGraphContext context;
|
||||
|
||||
#region Context Shortcuts
|
||||
|
||||
private static GraphReference reference => context.reference;
|
||||
|
||||
private static IGraph graph => context.graph;
|
||||
|
||||
private static ICanvas canvas => context.canvas;
|
||||
|
||||
private static GraphSelection selection => context.selection;
|
||||
|
||||
#endregion
|
||||
|
||||
public static Clipboard singleClipboard { get; }
|
||||
|
||||
public static Clipboard groupClipboard { get; }
|
||||
|
||||
public static bool canCopySelection => selection.Count > 0;
|
||||
|
||||
public static bool canPaste
|
||||
{
|
||||
get
|
||||
{
|
||||
if (selection.Count == 1 && CanPasteInside(selection.Single()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return canPasteOutside;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool canPasteOutside => groupClipboard.containsData && GetPasteGroup().Any();
|
||||
|
||||
public static bool canDuplicateSelection => GetCopyGroup(selection).Count > 0;
|
||||
|
||||
private static HashSet<IGraphElement> GetCopyGroup(IEnumerable<IGraphElement> elements)
|
||||
{
|
||||
var copyGroup = new HashSet<IGraphElement>();
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
copyGroup.Add(element);
|
||||
|
||||
canvas.Widget(element).ExpandCopyGroup(copyGroup);
|
||||
}
|
||||
|
||||
canvas.ShrinkCopyGroup(copyGroup);
|
||||
|
||||
return copyGroup;
|
||||
}
|
||||
|
||||
private static List<IGraphElement> GetPasteGroup()
|
||||
{
|
||||
return groupClipboard.Paste<HashSet<IGraphElement>>()
|
||||
.Where(e => graph.elements.Includes(e.GetType()))
|
||||
.OrderBy(e => e.dependencyOrder)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static void CopyElement(IGraphElement element)
|
||||
{
|
||||
Ensure.That(nameof(element)).IsNotNull(element);
|
||||
|
||||
singleClipboard.Copy(element);
|
||||
groupClipboard.Copy(GetCopyGroup(element.Yield()));
|
||||
}
|
||||
|
||||
public static void CopySelection()
|
||||
{
|
||||
if (!canCopySelection)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (selection.Count == 1)
|
||||
{
|
||||
CopyElement(selection.Single());
|
||||
}
|
||||
else
|
||||
{
|
||||
singleClipboard.Clear();
|
||||
groupClipboard.Copy(GetCopyGroup(selection));
|
||||
}
|
||||
|
||||
e?.TryUse();
|
||||
}
|
||||
|
||||
public static void Paste(Vector2? position = null)
|
||||
{
|
||||
if (!canPaste)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (selection.Count == 1 && CanPasteInside(selection.Single()))
|
||||
{
|
||||
PasteInside(selection.Single());
|
||||
}
|
||||
else
|
||||
{
|
||||
PasteOutside(true, position);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CanPasteInside(IGraphElement element)
|
||||
{
|
||||
Ensure.That(nameof(element)).IsNotNull(element);
|
||||
|
||||
// TODO: A solid PasteInside implementation would work like ReplaceUnit:
|
||||
// Implement an IPreservable interface, preserve, remove, recreate, apply.
|
||||
// This would make entirely sure that all OnAdd/OnRemove handlers get called,
|
||||
// and wouldn't require any per-element implementation. Plus, it would
|
||||
// allow pasting across element types while preserving connections/transitions!
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void PasteInside(IGraphElement element)
|
||||
{
|
||||
Ensure.That(nameof(element)).IsNotNull(element);
|
||||
|
||||
if (!CanPasteInside(element))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
UndoUtility.RecordEditedObject("Paste Graph Element");
|
||||
|
||||
throw new NotImplementedException();
|
||||
|
||||
//GUI.changed = true;
|
||||
//e?.TryUse();
|
||||
}
|
||||
|
||||
public static void PasteOutside(bool reposition, Vector2? position = null)
|
||||
{
|
||||
if (!canPasteOutside)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
UndoUtility.RecordEditedObject("Paste Graph Elements");
|
||||
|
||||
var pastedElements = GetPasteGroup();
|
||||
|
||||
// Assign new GUIDs
|
||||
|
||||
foreach (var pastedElement in pastedElements)
|
||||
{
|
||||
pastedElement.guid = Guid.NewGuid();
|
||||
}
|
||||
|
||||
// Add elements to graph and selection
|
||||
|
||||
selection.Clear();
|
||||
|
||||
foreach (var pastedElement in pastedElements)
|
||||
{
|
||||
if (!pastedElement.HandleDependencies())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
graph.elements.Add(pastedElement);
|
||||
|
||||
selection.Add(pastedElement);
|
||||
}
|
||||
|
||||
canvas.Cache();
|
||||
|
||||
foreach (var pastedElement in pastedElements)
|
||||
{
|
||||
var pastedWidget = canvas.Widget(pastedElement);
|
||||
pastedWidget.BringToFront();
|
||||
}
|
||||
|
||||
var pastedWidgets = pastedElements.Select(e => canvas.Widget(e)).ToList();
|
||||
|
||||
// Recenter elements in graph view
|
||||
|
||||
if (reposition)
|
||||
{
|
||||
var area = GraphGUI.CalculateArea(pastedWidgets.Where(widget => widget.canDrag));
|
||||
|
||||
Vector2 delta;
|
||||
|
||||
if (position.HasValue)
|
||||
{
|
||||
delta = position.Value - area.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta = graph.pan - area.center;
|
||||
}
|
||||
|
||||
foreach (var pastedWidget in pastedWidgets)
|
||||
{
|
||||
if (pastedWidget.canDrag)
|
||||
{
|
||||
pastedWidget.position = new Rect(pastedWidget.position.position + delta, pastedWidget.position.size).PixelPerfect();
|
||||
pastedWidget.Reposition();
|
||||
pastedWidget.CachePositionFirstPass();
|
||||
pastedWidget.CachePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Space out overlapping elements
|
||||
|
||||
foreach (var pastedWidget in pastedWidgets)
|
||||
{
|
||||
if (pastedWidget.canDrag)
|
||||
{
|
||||
var distanciation = 20;
|
||||
var timeout = 100;
|
||||
var timeoutIndex = 0;
|
||||
|
||||
while (GraphGUI.PositionOverlaps(canvas, pastedWidget, 5))
|
||||
{
|
||||
// Space the widget out
|
||||
pastedWidget.position = new Rect(pastedWidget.position.position + new Vector2(distanciation, distanciation), pastedWidget.position.size).PixelPerfect();
|
||||
|
||||
// Calculate the resulting position immediately
|
||||
pastedWidget.CachePositionFirstPass();
|
||||
pastedWidget.CachePosition();
|
||||
|
||||
// Mark it as invalid still so dependencies like ports will be recached
|
||||
pastedWidget.Reposition();
|
||||
|
||||
// Failsafe to keep the editor from freezing
|
||||
if (++timeoutIndex > timeout)
|
||||
{
|
||||
Debug.LogWarning($"Failed to space out pasted element: {pastedWidget.element}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Cache();
|
||||
|
||||
GUI.changed = true;
|
||||
|
||||
e?.TryUse();
|
||||
}
|
||||
|
||||
public static void CutSelection()
|
||||
{
|
||||
UndoUtility.RecordEditedObject("Cut Graph Element Selection");
|
||||
CopySelection();
|
||||
canvas.DeleteSelection();
|
||||
}
|
||||
|
||||
public static void DuplicateSelection()
|
||||
{
|
||||
object singleClipboardRestore = null;
|
||||
object groupClipboardRestore = null;
|
||||
|
||||
if (singleClipboard.containsData)
|
||||
{
|
||||
singleClipboardRestore = singleClipboard.Paste();
|
||||
}
|
||||
|
||||
if (groupClipboard.containsData)
|
||||
{
|
||||
groupClipboardRestore = groupClipboard.Paste();
|
||||
}
|
||||
|
||||
UndoUtility.RecordEditedObject("Duplicate Graph Element Selection");
|
||||
CopySelection();
|
||||
PasteOutside(false);
|
||||
|
||||
if (singleClipboardRestore != null)
|
||||
{
|
||||
singleClipboard.Copy(singleClipboardRestore);
|
||||
}
|
||||
else
|
||||
{
|
||||
singleClipboard.Clear();
|
||||
}
|
||||
|
||||
if (groupClipboardRestore != null)
|
||||
{
|
||||
groupClipboard.Copy(groupClipboardRestore);
|
||||
}
|
||||
else
|
||||
{
|
||||
groupClipboard.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class GraphContext<TGraph, TCanvas> : IGraphContext
|
||||
where TGraph : class, IGraph
|
||||
where TCanvas : class, ICanvas
|
||||
{
|
||||
protected GraphContext(GraphReference reference)
|
||||
{
|
||||
Ensure.That(nameof(reference)).IsNotNull(reference);
|
||||
Ensure.That(nameof(reference.graph)).IsOfType<TGraph>(reference.graph);
|
||||
|
||||
this.reference = reference;
|
||||
|
||||
analyserProvider = new AnalyserProvider(reference);
|
||||
graphMetadata = Metadata.Root().StaticObject(reference.graph);
|
||||
extensions = this.Extensions().ToList().AsReadOnly();
|
||||
sidebarPanels = SidebarPanels().ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public GraphReference reference { get; }
|
||||
|
||||
public AnalyserProvider analyserProvider { get; }
|
||||
|
||||
public TCanvas canvas => (TCanvas)graph.Canvas();
|
||||
|
||||
ICanvas IGraphContext.canvas => canvas;
|
||||
|
||||
public GraphSelection selection => canvas.selection;
|
||||
|
||||
public TGraph graph => (TGraph)reference.graph;
|
||||
|
||||
IGraph IGraphContext.graph => graph;
|
||||
|
||||
public Metadata graphMetadata { get; }
|
||||
|
||||
public Metadata selectionMetadata => selection.Count == 1 ? ElementMetadata(selection.Single()) : null;
|
||||
|
||||
public Metadata ElementMetadata(IGraphElement element)
|
||||
{
|
||||
// Static object is faster than indexer
|
||||
// return graphMetadata[nameof(IGraph.elements)].Indexer(element.guid).Cast(element.GetType());
|
||||
return graphMetadata[nameof(IGraph.elements)].StaticObject(element);
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<IGraphContextExtension> extensions { get; }
|
||||
|
||||
IEnumerable<IGraphContextExtension> IGraphContext.extensions => extensions;
|
||||
|
||||
public virtual string windowTitle => "Graph";
|
||||
|
||||
protected virtual IEnumerable<ISidebarPanelContent> SidebarPanels() => Enumerable.Empty<ISidebarPanelContent>();
|
||||
|
||||
public ReadOnlyCollection<ISidebarPanelContent> sidebarPanels { get; }
|
||||
|
||||
IEnumerable<ISidebarPanelContent> IGraphContext.sidebarPanels => sidebarPanels;
|
||||
|
||||
public bool isPrefabInstance => reference.serializedObject?.IsConnectedPrefabInstance() ?? false;
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Manually free the providers so that the
|
||||
// disposable decorators free their event handlers
|
||||
|
||||
analyserProvider?.FreeAll();
|
||||
}
|
||||
|
||||
public void BeginEdit(bool disablePrefabInstance = true)
|
||||
{
|
||||
LudiqEditorUtility.editedObject.BeginOverride(reference.serializedObject);
|
||||
LudiqGraphsEditorUtility.editedContext.BeginOverride(this);
|
||||
EditorGUI.BeginDisabledGroup(disablePrefabInstance && isPrefabInstance);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
}
|
||||
|
||||
public void EndEdit()
|
||||
{
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
DescribeAndAnalyze();
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
LudiqGraphsEditorUtility.editedContext.EndOverride();
|
||||
LudiqEditorUtility.editedObject.EndOverride();
|
||||
}
|
||||
|
||||
public void DescribeAndAnalyze()
|
||||
{
|
||||
foreach (var element in graph.elements)
|
||||
{
|
||||
if (element.HasDescriptor())
|
||||
{
|
||||
element.Describe();
|
||||
}
|
||||
}
|
||||
|
||||
graph.Describe();
|
||||
|
||||
analyserProvider.AnalyzeAll();
|
||||
|
||||
reference.parent.Describe();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class GraphContextAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public GraphContextAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class GraphContextExtension<TGraphContext> : IGraphContextExtension
|
||||
where TGraphContext : IGraphContext
|
||||
{
|
||||
protected GraphContextExtension(TGraphContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TGraphContext context { get; }
|
||||
|
||||
#region Context Shortcuts
|
||||
|
||||
protected GraphReference reference => context.reference;
|
||||
|
||||
protected IGraph graph => context.graph;
|
||||
|
||||
protected ICanvas canvas => context.canvas;
|
||||
|
||||
protected GraphSelection selection => context.selection;
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual IEnumerable<GraphContextMenuItem> contextMenuItems => Enumerable.Empty<GraphContextMenuItem>();
|
||||
|
||||
public virtual DragAndDropVisualMode dragAndDropVisualMode => DragAndDropVisualMode.Generic;
|
||||
|
||||
public virtual bool AcceptsDragAndDrop() => false;
|
||||
|
||||
public virtual void PerformDragAndDrop() { }
|
||||
|
||||
public virtual void UpdateDragAndDrop() { }
|
||||
|
||||
public virtual void DrawDragAndDropPreview() { }
|
||||
|
||||
public virtual void ExitDragAndDrop() { }
|
||||
|
||||
protected static Event e => Event.current;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class GraphContextExtensionAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public GraphContextExtensionAttribute(Type type)
|
||||
{
|
||||
Ensure.That(nameof(type)).IsNotNull(type);
|
||||
Ensure.That(nameof(type)).IsOfType(type, typeof(IGraphContext));
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphContextExtensionProvider : MultiDecoratorProvider<IGraphContext, IGraphContextExtension, GraphContextExtensionAttribute>
|
||||
{
|
||||
static GraphContextExtensionProvider()
|
||||
{
|
||||
instance = new GraphContextExtensionProvider();
|
||||
}
|
||||
|
||||
public static GraphContextExtensionProvider instance { get; }
|
||||
}
|
||||
|
||||
public static class XCanvasExtensionProvider
|
||||
{
|
||||
public static IEnumerable<IGraphContextExtension> Extensions(this IGraphContext context)
|
||||
{
|
||||
return GraphContextExtensionProvider.instance.GetDecorators(context);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public struct GraphContextMenuItem
|
||||
{
|
||||
public Action<Vector2> action { get; }
|
||||
public string label { get; }
|
||||
|
||||
public GraphContextMenuItem(Action<Vector2> action, string label)
|
||||
{
|
||||
this.action = action;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphContextProvider : SingleDecoratorProvider<GraphReference, IGraphContext, GraphContextAttribute>
|
||||
{
|
||||
static GraphContextProvider()
|
||||
{
|
||||
instance = new GraphContextProvider();
|
||||
}
|
||||
|
||||
public static GraphContextProvider instance { get; }
|
||||
|
||||
protected override bool cache => true;
|
||||
|
||||
protected override Type GetDecoratedType(GraphReference reference)
|
||||
{
|
||||
return reference.graph.GetType();
|
||||
}
|
||||
|
||||
public override bool IsValid(GraphReference reference)
|
||||
{
|
||||
return reference.isValid;
|
||||
}
|
||||
}
|
||||
|
||||
public static class XGraphContextProvider
|
||||
{
|
||||
public static IGraphContext Context(this GraphReference reference)
|
||||
{
|
||||
return GraphContextProvider.instance.GetDecorator(reference);
|
||||
}
|
||||
|
||||
public static T Context<T>(this GraphReference reference) where T : IGraphContext
|
||||
{
|
||||
return GraphContextProvider.instance.GetDecorator<T>(reference);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class GraphSelection : ISet<IGraphElement>
|
||||
{
|
||||
public GraphSelection() : base()
|
||||
{
|
||||
set = new HashSet<IGraphElement>();
|
||||
}
|
||||
|
||||
public event Action changed;
|
||||
|
||||
private readonly HashSet<IGraphElement> set;
|
||||
|
||||
public int Count => set.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
private void OnChange()
|
||||
{
|
||||
changed?.Invoke();
|
||||
}
|
||||
|
||||
IEnumerator<IGraphElement> IEnumerable<IGraphElement>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public HashSet<IGraphElement>.Enumerator GetEnumerator()
|
||||
{
|
||||
return set.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Select(IGraphElement item)
|
||||
{
|
||||
Clear();
|
||||
Add(item);
|
||||
}
|
||||
|
||||
public void Select(IEnumerable<IGraphElement> items)
|
||||
{
|
||||
Clear();
|
||||
UnionWith(items);
|
||||
}
|
||||
|
||||
public bool Contains(IGraphElement item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
return set.Contains(item);
|
||||
}
|
||||
|
||||
public bool Add(IGraphElement item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
if (set.Add(item))
|
||||
{
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(IGraphElement item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
if (set.Remove(item))
|
||||
{
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (set.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
set.Clear();
|
||||
OnChange();
|
||||
}
|
||||
|
||||
#region Set Logic
|
||||
|
||||
public void ExceptWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.ExceptWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void IntersectWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.IntersectWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.SymmetricExceptWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.UnionWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.Overlaps(other);
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.SetEquals(other);
|
||||
}
|
||||
|
||||
public int RemoveWhere(Predicate<IGraphElement> match)
|
||||
{
|
||||
return set.RemoveWhere(match);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection
|
||||
|
||||
void ICollection<IGraphElement>.Add(IGraphElement item)
|
||||
{
|
||||
if (set.Add(item))
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(IGraphElement[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var item in this)
|
||||
{
|
||||
array[i + arrayIndex] = item;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphContext : IDisposable
|
||||
{
|
||||
GraphReference reference { get; }
|
||||
|
||||
IEnumerable<IGraphContextExtension> extensions { get; }
|
||||
|
||||
IGraph graph { get; }
|
||||
|
||||
ICanvas canvas { get; }
|
||||
|
||||
GraphSelection selection { get; }
|
||||
|
||||
Metadata graphMetadata { get; }
|
||||
|
||||
Metadata selectionMetadata { get; }
|
||||
|
||||
Metadata ElementMetadata(IGraphElement element);
|
||||
|
||||
AnalyserProvider analyserProvider { get; }
|
||||
|
||||
string windowTitle { get; }
|
||||
|
||||
IEnumerable<ISidebarPanelContent> sidebarPanels { get; }
|
||||
|
||||
bool isPrefabInstance { get; }
|
||||
|
||||
void BeginEdit(bool disablePrefabInstance = true);
|
||||
|
||||
void EndEdit();
|
||||
|
||||
void DescribeAndAnalyze();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[InitializeAfterPlugins]
|
||||
public class GraphDebugDataProvider
|
||||
{
|
||||
static GraphDebugDataProvider()
|
||||
{
|
||||
GraphPointer.fetchRootDebugDataBinding = FetchRootDebugData;
|
||||
}
|
||||
|
||||
private static IGraphDebugData FetchRootDebugData(IGraphRoot root)
|
||||
{
|
||||
if (!rootDatas.TryGetValue(root, out var rootData))
|
||||
{
|
||||
rootData = new GraphDebugData(root.childGraph);
|
||||
rootDatas.Add(root, rootData);
|
||||
}
|
||||
|
||||
return rootData;
|
||||
}
|
||||
|
||||
private static Dictionary<IGraphRoot, IGraphDebugData> rootDatas = new Dictionary<IGraphRoot, IGraphDebugData>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class MultiDecoratorProvider<TDecorated, TDecorator, TAttribute>
|
||||
where TAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
protected MultiDecoratorProvider()
|
||||
{
|
||||
definedDecoratorTypes = new Dictionary<Type, HashSet<Type>>();
|
||||
resolvedDecoratorTypes = new Dictionary<Type, HashSet<Type>>();
|
||||
decorators = new Dictionary<TDecorated, HashSet<TDecorator>>();
|
||||
|
||||
foreach (var decoratorType in typeset.Where(t => t.HasAttribute<TAttribute>(false)))
|
||||
{
|
||||
foreach (var decoratedType in decoratorType.GetAttributes<TAttribute>(false).Select(a => a.type))
|
||||
{
|
||||
if (!definedDecoratorTypes.ContainsKey(decoratedType))
|
||||
{
|
||||
definedDecoratorTypes.Add(decoratedType, new HashSet<Type>());
|
||||
}
|
||||
|
||||
definedDecoratorTypes[decoratedType].Add(decoratorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<Type> typeset => Codebase.editorTypes;
|
||||
|
||||
private readonly Dictionary<TDecorated, HashSet<TDecorator>> decorators;
|
||||
protected readonly Dictionary<Type, HashSet<Type>> definedDecoratorTypes;
|
||||
private readonly Dictionary<Type, HashSet<Type>> resolvedDecoratorTypes;
|
||||
|
||||
protected virtual IEnumerable<TDecorator> CreateDecorators(TDecorated decorated)
|
||||
{
|
||||
return GetDecoratorTypes(decorated.GetType()).Select(t => (TDecorator)t.Instantiate(false, decorated));
|
||||
}
|
||||
|
||||
protected virtual bool ShouldInvalidateDecorators(TDecorated decorated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<TDecorator> GetDecorators(TDecorated decorated)
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
if (decorators.ContainsKey(decorated) && ShouldInvalidateDecorators(decorated))
|
||||
{
|
||||
foreach (var disposableDecorator in decorators[decorated].OfType<IDisposable>())
|
||||
{
|
||||
disposableDecorator.Dispose();
|
||||
}
|
||||
|
||||
decorators.Remove(decorated);
|
||||
}
|
||||
|
||||
if (!decorators.ContainsKey(decorated))
|
||||
{
|
||||
decorators.Add(decorated, CreateDecorators(decorated).ToHashSet());
|
||||
}
|
||||
|
||||
return decorators[decorated];
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TSpecificDecorator> GetDecorators<TSpecificDecorator>(TDecorated decorated) where TSpecificDecorator : TDecorator
|
||||
{
|
||||
return GetDecorators(decorated).OfType<TSpecificDecorator>();
|
||||
}
|
||||
|
||||
public virtual void ClearCache()
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
foreach (var decorator in decorators.SelectMany(kvp => kvp.Value).OfType<IDisposable>())
|
||||
{
|
||||
decorator.Dispose();
|
||||
}
|
||||
|
||||
decorators.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasDecorator(Type type)
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
if (!resolvedDecoratorTypes.ContainsKey(type))
|
||||
{
|
||||
resolvedDecoratorTypes.Add(type, ResolveDecoratorTypes(type).ToHashSet());
|
||||
}
|
||||
|
||||
return resolvedDecoratorTypes[type] != null;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Type> GetDecoratorTypes(Type type)
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
if (!HasDecorator(type))
|
||||
{
|
||||
throw new NotSupportedException($"Cannot resolve decorator type for '{type}'.");
|
||||
}
|
||||
|
||||
return resolvedDecoratorTypes[type];
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<Type> ResolveDecoratorTypes(Type type)
|
||||
{
|
||||
return ResolveDecoratorTypesByHierarchy(type, true);
|
||||
}
|
||||
|
||||
protected IEnumerable<Type> ResolveDecoratorTypesByHierarchy(Type type, bool inherit)
|
||||
{
|
||||
// We traverse the tree recursively and manually instead of
|
||||
// using Linq to find any "assignable from" type in the defined
|
||||
// decorators list in order to preserve priority.
|
||||
|
||||
// For example, in an A : B : C chain where we have decorators
|
||||
// for B and C, this method will always map A to B, not A to C.
|
||||
|
||||
var resolved = DirectResolve(type) ?? GenericResolve(type);
|
||||
|
||||
if (resolved != null)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
if (inherit)
|
||||
{
|
||||
foreach (var baseTypeOrInterface in type.BaseTypeAndInterfaces(false))
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypesByHierarchy(baseTypeOrInterface, false);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.BaseType != null)
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypesByHierarchy(type.BaseType, true);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> DirectResolve(Type type)
|
||||
{
|
||||
if (definedDecoratorTypes.ContainsKey(type))
|
||||
{
|
||||
return definedDecoratorTypes[type];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GenericResolve(Type type)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var typeDefinition = type.GetGenericTypeDefinition();
|
||||
|
||||
if (definedDecoratorTypes.ContainsKey(typeDefinition))
|
||||
{
|
||||
foreach (var definedDecoratorType in definedDecoratorTypes[typeDefinition])
|
||||
{
|
||||
// For example: [Decorator(List<>)] ListDecorator<T> gets passed T of the item
|
||||
if (definedDecoratorType.ContainsGenericParameters)
|
||||
{
|
||||
yield return definedDecoratorType.MakeGenericType(type.GetGenericArguments());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return definedDecoratorType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class SingleDecoratorProvider<TDecorated, TDecorator, TAttribute>
|
||||
where TAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
protected readonly object typesLock = new object();
|
||||
protected readonly object instancesLock = new object();
|
||||
|
||||
protected SingleDecoratorProvider()
|
||||
{
|
||||
definedDecoratorTypes = new Dictionary<Type, Type>();
|
||||
resolvedDecoratorTypes = new Dictionary<Type, Type>();
|
||||
|
||||
decorators = new Dictionary<TDecorated, TDecorator>();
|
||||
decorateds = new Dictionary<TDecorator, TDecorated>();
|
||||
|
||||
MapAttributeTypes();
|
||||
|
||||
Freed();
|
||||
|
||||
EditorApplication.update += FreeIfNeeded;
|
||||
}
|
||||
|
||||
protected virtual TDecorator CreateDecorator(Type decoratorType, TDecorated decorated)
|
||||
{
|
||||
return (TDecorator)decoratorType.Instantiate(false, decorated);
|
||||
}
|
||||
|
||||
private TDecorator CreateDecorator(TDecorated decorated)
|
||||
{
|
||||
if (!IsValid(decorated))
|
||||
{
|
||||
throw new InvalidOperationException($"Decorated object is not valid: {decorated}");
|
||||
}
|
||||
|
||||
return CreateDecorator(GetDecoratorType(GetDecoratedType(decorated)), decorated);
|
||||
}
|
||||
|
||||
#region Type Resolution
|
||||
|
||||
// By restricting our search in editor types, we greatly reduce the enumeration
|
||||
// and therefore the attribute caching time on initialization
|
||||
protected virtual IEnumerable<Type> typeset => Codebase.editorTypes;
|
||||
|
||||
protected readonly Dictionary<Type, Type> definedDecoratorTypes;
|
||||
|
||||
protected readonly Dictionary<Type, Type> resolvedDecoratorTypes;
|
||||
|
||||
private void MapAttributeTypes()
|
||||
{
|
||||
foreach (var decoratorType in typeset.Where(t => t.HasAttribute<TAttribute>(false)))
|
||||
{
|
||||
foreach (var decoratedType in decoratorType.GetAttributes<TAttribute>(false).Select(a => a.type))
|
||||
{
|
||||
if (definedDecoratorTypes.ContainsKey(decoratedType))
|
||||
{
|
||||
Debug.LogWarning($"Multiple '{typeof(TDecorator)}' for '{decoratedType}'. Ignoring '{decoratorType}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
definedDecoratorTypes.Add(decoratedType, decoratorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasDecorator(Type decoratedType)
|
||||
{
|
||||
return TryGetDecoratorType(decoratedType, out var decoratorType);
|
||||
}
|
||||
|
||||
public bool TryGetDecoratorType(Type decoratedType, out Type decoratorType)
|
||||
{
|
||||
lock (typesLock)
|
||||
{
|
||||
Ensure.That(nameof(decoratedType)).IsNotNull(decoratedType);
|
||||
|
||||
if (!resolvedDecoratorTypes.TryGetValue(decoratedType, out decoratorType))
|
||||
{
|
||||
decoratorType = ResolveDecoratorType(decoratedType);
|
||||
resolvedDecoratorTypes.Add(decoratedType, decoratorType);
|
||||
}
|
||||
|
||||
return decoratorType != null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type GetDecoratedType(TDecorated decorated)
|
||||
{
|
||||
return decorated.GetType();
|
||||
}
|
||||
|
||||
public Type GetDecoratorType(Type decoratedType)
|
||||
{
|
||||
lock (typesLock)
|
||||
{
|
||||
Ensure.That(nameof(decoratedType)).IsNotNull(decoratedType);
|
||||
|
||||
if (!TryGetDecoratorType(decoratedType, out var decoratorType))
|
||||
{
|
||||
throw new NotSupportedException($"Cannot decorate '{decoratedType}' with '{typeof(TDecorator)}'.");
|
||||
}
|
||||
|
||||
return decoratorType;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type ResolveDecoratorType(Type decoratedType)
|
||||
{
|
||||
return ResolveDecoratorTypeByHierarchy(decoratedType);
|
||||
}
|
||||
|
||||
protected Type ResolveDecoratorTypeByHierarchy(Type decoratedType, bool inherit = true)
|
||||
{
|
||||
// We traverse the tree recursively and manually instead of
|
||||
// using Linq to find any "assignable from" type in the defined
|
||||
// decorators list in order to preserve priority.
|
||||
|
||||
// For example, in an A : B : C chain where we have decorators
|
||||
// for B and C, this method will always map A to B, not A to C.
|
||||
|
||||
var resolved = DirectResolve(decoratedType) ?? GenericResolve(decoratedType);
|
||||
|
||||
if (resolved != null)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
if (inherit)
|
||||
{
|
||||
foreach (var baseTypeOrInterface in decoratedType.BaseTypeAndInterfaces(false))
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypeByHierarchy(baseTypeOrInterface, false);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (decoratedType.BaseType != null)
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypeByHierarchy(decoratedType.BaseType);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Type DirectResolve(Type decoratedType)
|
||||
{
|
||||
if (definedDecoratorTypes.ContainsKey(decoratedType))
|
||||
{
|
||||
var definedDecoratorType = definedDecoratorTypes[decoratedType];
|
||||
|
||||
if (definedDecoratorType.IsGenericTypeDefinition)
|
||||
{
|
||||
var arguments = definedDecoratorType.GetGenericArguments();
|
||||
|
||||
// For example: [Decorator(Decorated)] Decorator<TDecorated> gets properly closed-constructed with type
|
||||
if (arguments.Length == 1 && arguments[0].CanMakeGenericTypeVia(decoratedType))
|
||||
{
|
||||
return definedDecoratorType.MakeGenericType(decoratedType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return definedDecoratorTypes[decoratedType];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Type GenericResolve(Type decoratedType)
|
||||
{
|
||||
if (decoratedType.IsGenericType)
|
||||
{
|
||||
var typeDefinition = decoratedType.GetGenericTypeDefinition();
|
||||
|
||||
if (definedDecoratorTypes.ContainsKey(typeDefinition))
|
||||
{
|
||||
var definedDecoratorType = definedDecoratorTypes[typeDefinition];
|
||||
|
||||
// For example: [Decorator(List<>)] ListDecorator<T> gets passed T of the item
|
||||
if (definedDecoratorType.ContainsGenericParameters)
|
||||
{
|
||||
return definedDecoratorType.MakeGenericType(decoratedType.GetGenericArguments());
|
||||
}
|
||||
else
|
||||
{
|
||||
return definedDecoratorType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Cache
|
||||
|
||||
protected readonly Dictionary<TDecorated, TDecorator> decorators;
|
||||
|
||||
protected readonly Dictionary<TDecorator, TDecorated> decorateds;
|
||||
|
||||
protected abstract bool cache { get; }
|
||||
|
||||
public abstract bool IsValid(TDecorated decorated);
|
||||
|
||||
public TDecorator GetDecorator(TDecorated decorated)
|
||||
{
|
||||
Ensure.That(nameof(decorated)).IsNotNull(decorated);
|
||||
|
||||
if (!cache)
|
||||
{
|
||||
var decorator = CreateDecorator(decorated);
|
||||
(decorator as IInitializable)?.Initialize();
|
||||
return decorator;
|
||||
}
|
||||
|
||||
lock (instancesLock)
|
||||
{
|
||||
var decoratorExists = decorators.TryGetValue(decorated, out var decorator);
|
||||
|
||||
if (decoratorExists && !IsValid(decorateds[decorator]))
|
||||
{
|
||||
Free(decorator);
|
||||
|
||||
decoratorExists = false;
|
||||
}
|
||||
|
||||
if (!decoratorExists)
|
||||
{
|
||||
decorator = CreateDecorator(decorated);
|
||||
|
||||
decorators.Add(decorated, decorator);
|
||||
decorateds.Add(decorator, decorated);
|
||||
|
||||
(decorator as IInitializable)?.Initialize();
|
||||
}
|
||||
|
||||
return decorator;
|
||||
}
|
||||
}
|
||||
|
||||
public T GetDecorator<T>(TDecorated decorated) where T : TDecorator
|
||||
{
|
||||
var decorator = GetDecorator(decorated);
|
||||
|
||||
if (!(decorator is T))
|
||||
{
|
||||
throw new InvalidCastException($"Decorator type mismatch for '{decorated}': found {decorator.GetType()}, expected {typeof(T)}.");
|
||||
}
|
||||
|
||||
return (T)decorator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Collection
|
||||
|
||||
private DateTime lastFreeTime;
|
||||
|
||||
protected virtual TimeSpan freeInterval => TimeSpan.FromSeconds(5);
|
||||
|
||||
private void Freed()
|
||||
{
|
||||
lastFreeTime = DateTime.Now;
|
||||
}
|
||||
|
||||
private bool shouldFree => cache && DateTime.Now > lastFreeTime + freeInterval;
|
||||
|
||||
private void FreeIfNeeded()
|
||||
{
|
||||
if (shouldFree)
|
||||
{
|
||||
FreeInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(TDecorator decorator)
|
||||
{
|
||||
lock (instancesLock)
|
||||
{
|
||||
if (decorateds.ContainsKey(decorator))
|
||||
{
|
||||
(decorator as IDisposable)?.Dispose();
|
||||
var decorated = decorateds[decorator];
|
||||
decorateds.Remove(decorator);
|
||||
decorators.Remove(decorated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(IEnumerable<TDecorator> decorators)
|
||||
{
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
Free(decorator);
|
||||
}
|
||||
}
|
||||
|
||||
public void FreeInvalid()
|
||||
{
|
||||
if (!cache)
|
||||
{
|
||||
Debug.LogWarning($"Trying to free a decorator provider without caching: {this}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (instancesLock)
|
||||
{
|
||||
Free(decorators.Where(d => !IsValid(d.Key)).Select(d => d.Value).ToArray());
|
||||
Freed();
|
||||
}
|
||||
}
|
||||
|
||||
public void FreeAll()
|
||||
{
|
||||
if (!cache)
|
||||
{
|
||||
Debug.LogWarning($"Trying to free a decorator provider without caching: {this}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (instancesLock)
|
||||
{
|
||||
Free(decorators.Values.ToArray());
|
||||
Freed();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Renew<TSpecificDecorator>(ref TSpecificDecorator decorator, TDecorated decorated, Func<TDecorated, TSpecificDecorator> constructor = null) where TSpecificDecorator : TDecorator
|
||||
{
|
||||
if (decorator == null || !IsValid(decorated))
|
||||
{
|
||||
if (constructor != null)
|
||||
{
|
||||
decorator = constructor(decorated);
|
||||
}
|
||||
else
|
||||
{
|
||||
decorator = (TSpecificDecorator)CreateDecorator(typeof(TSpecificDecorator), decorated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory methods that create <see cref="IElementAdderMenuBuilder{TContext}" />
|
||||
/// instances that can then be used to build element adder menus.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <para>
|
||||
/// The following example demonstrates how to build and display a menu which
|
||||
/// allows the user to add elements to a given context object upon clicking a button:
|
||||
/// </para>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// public class ShoppingListElementAdder : IElementAdder<ShoppingList> {
|
||||
/// public ShoppingListElementAdder(ShoppingList shoppingList) {
|
||||
/// Object = shoppingList;
|
||||
/// }
|
||||
///
|
||||
/// public ShoppingList Object { get; private set; }
|
||||
///
|
||||
/// public bool CanAddElement(Type type) {
|
||||
/// return true;
|
||||
/// }
|
||||
/// public object AddElement(Type type) {
|
||||
/// var instance = Activator.CreateInstance(type);
|
||||
/// shoppingList.Add((ShoppingItem)instance);
|
||||
/// return instance;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// private void DrawAddMenuButton(ShoppingList shoppingList) {
|
||||
/// var content = new GUIContent("Add Menu");
|
||||
/// Rect position = GUILayoutUtility.GetRect(content, GUI.skin.button);
|
||||
/// if (GUI.Button(position, content)) {
|
||||
/// var builder = ElementAdderMenuBuilder.For<ShoppingList>(ShoppingItem);
|
||||
/// builder.SetElementAdder(new ShoppingListElementAdder(shoppingList));
|
||||
/// var menu = builder.GetMenu();
|
||||
/// menu.DropDown(buttonPosition);
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// public class ShoppingListElementAdder extends IElementAdder.<ShoppingList> {
|
||||
/// var _object:ShoppingList;
|
||||
///
|
||||
/// function ShoppingListElementAdder(shoppingList:ShoppingList) {
|
||||
/// Object = shoppingList;
|
||||
/// }
|
||||
///
|
||||
/// function get Object():ShoppingList { return _object; }
|
||||
///
|
||||
/// function CanAddElement(type:Type):boolean {
|
||||
/// return true;
|
||||
/// }
|
||||
/// function AddElement(type:Type):System.Object {
|
||||
/// var instance = Activator.CreateInstance(type);
|
||||
/// shoppingList.Add((ShoppingItem)instance);
|
||||
/// return instance;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// function DrawAddMenuButton(shoppingList:ShoppingList) {
|
||||
/// var content = new GUIContent('Add Menu');
|
||||
/// var position = GUILayoutUtility.GetRect(content, GUI.skin.button);
|
||||
/// if (GUI.Button(position, content)) {
|
||||
/// var builder = ElementAdderMenuBuilder.For.<ShoppingList>(ShoppingItem);
|
||||
/// builder.SetElementAdder(new ShoppingListElementAdder(shoppingList));
|
||||
/// var menu = builder.GetMenu();
|
||||
/// menu.DropDown(buttonPosition);
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
public static class ElementAdderMenuBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IElementAdderMenuBuilder{TContext}" /> to build an element
|
||||
/// adder menu for a context object of the type <typeparamref name="TContext" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <returns>
|
||||
/// A new <see cref="IElementAdderMenuBuilder{TContext}" /> instance.
|
||||
/// </returns>
|
||||
/// <seealso cref="IElementAdderMenuBuilder{TContext}.SetContractType(Type)" />
|
||||
public static IElementAdderMenuBuilder<TContext> For<TContext>()
|
||||
{
|
||||
return new GenericElementAdderMenuBuilder<TContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IElementAdderMenuBuilder{TContext}" /> to build an element
|
||||
/// adder menu for a context object of the type <typeparamref name="TContext" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// A new <see cref="IElementAdderMenuBuilder{TContext}" /> instance.
|
||||
/// </returns>
|
||||
/// <seealso cref="IElementAdderMenuBuilder{TContext}.SetContractType(Type)" />
|
||||
public static IElementAdderMenuBuilder<TContext> For<TContext>(Type contractType)
|
||||
{
|
||||
var builder = For<TContext>();
|
||||
builder.SetContractType(contractType);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Annotate <see cref="IElementAdderMenuCommand{TContext}" /> implementations with a
|
||||
/// <see cref="ElementAdderMenuCommandAttribute" /> to associate it with the contract
|
||||
/// type of addable elements.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <para>
|
||||
/// The following source code demonstrates how to add a helper menu command to
|
||||
/// the add element menu of a shopping list:
|
||||
/// </para>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// [ElementAdderMenuCommand(typeof(ShoppingItem))]
|
||||
/// public class AddFavoriteShoppingItemsCommand : IElementAdderMenuCommand<ShoppingList> {
|
||||
/// public AddFavoriteShoppingItemsCommand() {
|
||||
/// Content = new GUIContent("Add Favorite Items");
|
||||
/// }
|
||||
///
|
||||
/// public GUIContent Content { get; private set; }
|
||||
///
|
||||
/// public bool CanExecute(IElementAdder<ShoppingList> elementAdder) {
|
||||
/// return true;
|
||||
/// }
|
||||
/// public void Execute(IElementAdder<ShoppingList> elementAdder) {
|
||||
/// // xTODO: Add favorite items to the shopping list!
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// @ElementAdderMenuCommand(ShoppingItem)
|
||||
/// class AddFavoriteShoppingItemsCommand extends IElementAdderMenuCommand.<ShoppingList> {
|
||||
/// var _content:GUIContent = new GUIContent('Add Favorite Items');
|
||||
///
|
||||
/// function get Content():GUIContent { return _content; }
|
||||
///
|
||||
/// function CanExecute(elementAdder:IElementAdder.<ShoppingList>):boolean {
|
||||
/// return true;
|
||||
/// }
|
||||
/// function Execute(elementAdder:IElementAdder.<ShoppingList>) {
|
||||
/// // xTODO: Add favorite items to the shopping list!
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class ElementAdderMenuCommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementAdderMenuCommandAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
public ElementAdderMenuCommandAttribute(Type contractType)
|
||||
{
|
||||
ContractType = contractType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contract type of addable elements.
|
||||
/// </summary>
|
||||
public Type ContractType { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides meta information which is useful when creating new implementations of
|
||||
/// the <see cref="IElementAdderMenuBuilder{TContext}" /> interface.
|
||||
/// </summary>
|
||||
public static class ElementAdderMeta
|
||||
{
|
||||
#region Adder Menu Command Types
|
||||
|
||||
private static Dictionary<Type, Dictionary<Type, List<Type>>> s_ContextMap = new Dictionary<Type, Dictionary<Type, List<Type>>>();
|
||||
|
||||
private static IEnumerable<Type> GetMenuCommandTypes<TContext>()
|
||||
{
|
||||
return
|
||||
from a in AppDomain.CurrentDomain.GetAssemblies()
|
||||
from t in a.GetTypesSafely()
|
||||
where t.IsClass && !t.IsAbstract && t.IsDefined(typeof(ElementAdderMenuCommandAttribute), false)
|
||||
where typeof(IElementAdderMenuCommand<TContext>).IsAssignableFrom(t)
|
||||
select t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of the <see cref="IElementAdderMenuCommand{TContext}" /> types
|
||||
/// that are associated with the specified <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// An array containing zero or more <see cref="System.Type" />.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetMenuCommands{TContext}(Type)" />
|
||||
public static Type[] GetMenuCommandTypes<TContext>(Type contractType)
|
||||
{
|
||||
if (contractType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contractType));
|
||||
}
|
||||
|
||||
Dictionary<Type, List<Type>> contractMap;
|
||||
List<Type> commandTypes;
|
||||
if (s_ContextMap.TryGetValue(typeof(TContext), out contractMap))
|
||||
{
|
||||
if (contractMap.TryGetValue(contractType, out commandTypes))
|
||||
{
|
||||
return commandTypes.ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
contractMap = new Dictionary<Type, List<Type>>();
|
||||
s_ContextMap[typeof(TContext)] = contractMap;
|
||||
}
|
||||
|
||||
commandTypes = new List<Type>();
|
||||
|
||||
foreach (var commandType in GetMenuCommandTypes<TContext>())
|
||||
{
|
||||
var attributes = (ElementAdderMenuCommandAttribute[])Attribute.GetCustomAttributes(commandType, typeof(ElementAdderMenuCommandAttribute));
|
||||
if (!attributes.Any(a => a.ContractType == contractType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
commandTypes.Add(commandType);
|
||||
}
|
||||
|
||||
contractMap[contractType] = commandTypes;
|
||||
return commandTypes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of <see cref="IElementAdderMenuCommand{TContext}" /> instances
|
||||
/// that are associated with the specified <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// An array containing zero or more <see cref="IElementAdderMenuCommand{TContext}" /> instances.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetMenuCommandTypes{TContext}(Type)" />
|
||||
public static IElementAdderMenuCommand<TContext>[] GetMenuCommands<TContext>(Type contractType)
|
||||
{
|
||||
var commandTypes = GetMenuCommandTypes<TContext>(contractType);
|
||||
var commands = new IElementAdderMenuCommand<TContext>[commandTypes.Length];
|
||||
for (var i = 0; i < commandTypes.Length; ++i)
|
||||
{
|
||||
commands[i] = (IElementAdderMenuCommand<TContext>)Activator.CreateInstance(commandTypes[i]);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Concrete Element Types
|
||||
|
||||
private static Dictionary<Type, Type[]> s_ConcreteElementTypes = new Dictionary<Type, Type[]>();
|
||||
|
||||
private static IEnumerable<Type> GetConcreteElementTypesHelper(Type contractType)
|
||||
{
|
||||
if (contractType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contractType));
|
||||
}
|
||||
|
||||
Type[] concreteTypes;
|
||||
if (!s_ConcreteElementTypes.TryGetValue(contractType, out concreteTypes))
|
||||
{
|
||||
concreteTypes =
|
||||
(from a in AppDomain.CurrentDomain.GetAssemblies()
|
||||
from t in a.GetTypesSafely()
|
||||
where t.IsClass && !t.IsAbstract && contractType.IsAssignableFrom(t)
|
||||
orderby t.Name
|
||||
select t
|
||||
).ToArray();
|
||||
s_ConcreteElementTypes[contractType] = concreteTypes;
|
||||
}
|
||||
|
||||
return concreteTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a filtered array of the concrete element types that implement the
|
||||
/// specified <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A type is excluded from the resulting array when one or more of the
|
||||
/// specified <paramref name="filters" /> returns a value of <c>false</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <param name="filters">An array of zero or more filters.</param>
|
||||
/// <returns>
|
||||
/// An array of zero or more concrete element types.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetConcreteElementTypes(Type)" />
|
||||
public static Type[] GetConcreteElementTypes(Type contractType, Func<Type, bool>[] filters)
|
||||
{
|
||||
return
|
||||
(from t in GetConcreteElementTypesHelper(contractType)
|
||||
where IsTypeIncluded(t, filters)
|
||||
select t
|
||||
).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of all the concrete element types that implement the specified
|
||||
/// <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// An array of zero or more concrete element types.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetConcreteElementTypes(Type, Func{Type, bool}[])" />
|
||||
public static Type[] GetConcreteElementTypes(Type contractType)
|
||||
{
|
||||
return GetConcreteElementTypesHelper(contractType).ToArray();
|
||||
}
|
||||
|
||||
private static bool IsTypeIncluded(Type concreteType, Func<Type, bool>[] filters)
|
||||
{
|
||||
if (filters != null)
|
||||
{
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (!filter(concreteType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
internal sealed class GenericElementAdderMenu : IElementAdderMenu
|
||||
{
|
||||
private GenericMenu _innerMenu = new GenericMenu();
|
||||
|
||||
public bool IsEmpty => _innerMenu.GetItemCount() == 0;
|
||||
|
||||
public void AddItem(GUIContent content, GenericMenu.MenuFunction handler)
|
||||
{
|
||||
_innerMenu.AddItem(content, false, handler);
|
||||
}
|
||||
|
||||
public void AddDisabledItem(GUIContent content)
|
||||
{
|
||||
_innerMenu.AddDisabledItem(content);
|
||||
}
|
||||
|
||||
public void AddSeparator(string path = "")
|
||||
{
|
||||
_innerMenu.AddSeparator(path);
|
||||
}
|
||||
|
||||
public void DropDown(Rect position)
|
||||
{
|
||||
_innerMenu.DropDown(position);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
internal sealed class GenericElementAdderMenuBuilder<TContext> : IElementAdderMenuBuilder<TContext>
|
||||
{
|
||||
public GenericElementAdderMenuBuilder()
|
||||
{
|
||||
_typeDisplayNameFormatter = NicifyTypeName;
|
||||
}
|
||||
|
||||
private Type _contractType;
|
||||
private IElementAdder<TContext> _elementAdder;
|
||||
private Func<Type, string> _typeDisplayNameFormatter;
|
||||
private List<Func<Type, bool>> _typeFilters = new List<Func<Type, bool>>();
|
||||
private List<IElementAdderMenuCommand<TContext>> _customCommands = new List<IElementAdderMenuCommand<TContext>>();
|
||||
|
||||
public void SetContractType(Type contractType)
|
||||
{
|
||||
_contractType = contractType;
|
||||
}
|
||||
|
||||
public void SetElementAdder(IElementAdder<TContext> elementAdder)
|
||||
{
|
||||
_elementAdder = elementAdder;
|
||||
}
|
||||
|
||||
public void SetTypeDisplayNameFormatter(Func<Type, string> formatter)
|
||||
{
|
||||
_typeDisplayNameFormatter = formatter ?? NicifyTypeName;
|
||||
}
|
||||
|
||||
public void AddTypeFilter(Func<Type, bool> typeFilter)
|
||||
{
|
||||
if (typeFilter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeFilter));
|
||||
}
|
||||
|
||||
_typeFilters.Add(typeFilter);
|
||||
}
|
||||
|
||||
public void AddCustomCommand(IElementAdderMenuCommand<TContext> command)
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
|
||||
_customCommands.Add(command);
|
||||
}
|
||||
|
||||
public IElementAdderMenu GetMenu()
|
||||
{
|
||||
var menu = new GenericElementAdderMenu();
|
||||
|
||||
AddCommandsToMenu(menu, _customCommands);
|
||||
|
||||
if (_contractType != null)
|
||||
{
|
||||
AddCommandsToMenu(menu, ElementAdderMeta.GetMenuCommands<TContext>(_contractType));
|
||||
AddConcreteTypesToMenu(menu, ElementAdderMeta.GetConcreteElementTypes(_contractType, _typeFilters.ToArray()));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void AddCommandsToMenu(GenericElementAdderMenu menu, IList<IElementAdderMenuCommand<TContext>> commands)
|
||||
{
|
||||
if (commands.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.IsEmpty)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
}
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
if (_elementAdder != null && command.CanExecute(_elementAdder))
|
||||
{
|
||||
menu.AddItem(command.Content, () => command.Execute(_elementAdder));
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(command.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddConcreteTypesToMenu(GenericElementAdderMenu menu, Type[] concreteTypes)
|
||||
{
|
||||
if (concreteTypes.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.IsEmpty)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
}
|
||||
|
||||
foreach (var concreteType in concreteTypes)
|
||||
{
|
||||
var content = new GUIContent(_typeDisplayNameFormatter(concreteType));
|
||||
if (_elementAdder != null && _elementAdder.CanAddElement(concreteType))
|
||||
{
|
||||
menu.AddItem(content, () =>
|
||||
{
|
||||
if (_elementAdder.CanAddElement(concreteType))
|
||||
{
|
||||
_elementAdder.AddElement(concreteType);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NicifyTypeName(Type type)
|
||||
{
|
||||
return ObjectNames.NicifyVariableName(type.Name);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an object which adds elements to a context object of the type
|
||||
/// <typeparamref name="TContext" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
public interface IElementAdder<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the context object.
|
||||
/// </summary>
|
||||
TContext Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a new element of the specified <paramref name="type" /> can
|
||||
/// be added to the associated context object.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of element to add.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if an element of the specified type can be added;
|
||||
/// otherwise, a value of <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanAddElement(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element of the specified <paramref name="type" /> to the associated
|
||||
/// context object.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of element to add.</param>
|
||||
/// <returns>
|
||||
/// The new element.
|
||||
/// </returns>
|
||||
object AddElement(Type type);
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a menu interface.
|
||||
/// </summary>
|
||||
public interface IElementAdderMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the menu contains any items.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the menu contains one or more items; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsEmpty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Displays the drop-down menu inside an editor GUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method should only be used during <b>OnGUI</b> and <b>OnSceneGUI</b>
|
||||
/// events; for instance, inside an editor window, a custom inspector or scene view.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position of menu button in the GUI.</param>
|
||||
void DropDown(Rect position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for building an <see cref="IElementAdderMenu" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
public interface IElementAdderMenuBuilder<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets contract type of the elements that can be included in the <see cref="IElementAdderMenu" />.
|
||||
/// Only non-abstract class types that are assignable from the <paramref name="contractType" />
|
||||
/// will be included in the built menu.
|
||||
/// </summary>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
void SetContractType(Type contractType);
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="IElementAdder{TContext}" /> implementation which is used
|
||||
/// when adding new elements to the context object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Specify a value of <c>null</c> for <paramref name="elementAdder" /> to
|
||||
/// prevent the selection of any types.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="elementAdder">Element adder.</param>
|
||||
void SetElementAdder(IElementAdder<TContext> elementAdder);
|
||||
|
||||
/// <summary>
|
||||
/// Set the function that formats the display of type names in the user interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Specify a value of <c>null</c> for <paramref name="formatter" /> to
|
||||
/// assume the default formatting function.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="formatter">Function that formats display name of type; or <c>null</c>.</param>
|
||||
void SetTypeDisplayNameFormatter(Func<Type, string> formatter);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a filter function which determines whether types can be included or
|
||||
/// whether they need to be excluded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If a filter function returns a value of <c>false</c> then that type
|
||||
/// will not be included in the menu interface.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="typeFilter">Filter function.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="typeFilter" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
void AddTypeFilter(Func<Type, bool> typeFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom command to the menu.
|
||||
/// </summary>
|
||||
/// <param name="command">The custom command.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="command" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
void AddCustomCommand(IElementAdderMenuCommand<TContext> command);
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns a new <see cref="IElementAdderMenu" /> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new <see cref="IElementAdderMenu" /> instance each time the method is invoked.
|
||||
/// </returns>
|
||||
IElementAdderMenu GetMenu();
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a menu command that can be included in an <see cref="IElementAdderMenu" />
|
||||
/// either by annotating an implementation of the <see cref="IElementAdderMenuCommand{TContext}" />
|
||||
/// interface with <see cref="ElementAdderMenuCommandAttribute" /> or directly by
|
||||
/// calling <see cref="IElementAdderMenuBuilder{TContext}.AddCustomCommand" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
public interface IElementAdderMenuCommand<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the content of the menu command.
|
||||
/// </summary>
|
||||
GUIContent Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the command can be executed.
|
||||
/// </summary>
|
||||
/// <param name="elementAdder">
|
||||
/// The associated element adder provides access to
|
||||
/// the <typeparamref name="TContext" /> instance.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if the command can execute; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanExecute(IElementAdder<TContext> elementAdder);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command.
|
||||
/// </summary>
|
||||
/// <param name="elementAdder">
|
||||
/// The associated element adder provides access to
|
||||
/// the <typeparamref name="TContext" /> instance.
|
||||
/// </param>
|
||||
void Execute(IElementAdder<TContext> elementAdder);
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Reorderable list adaptor for generic list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This adaptor can be subclassed to add special logic to item height calculation.
|
||||
/// You may want to implement a custom adaptor class where specialised functionality
|
||||
/// is needed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// List elements which implement the <see cref="System.ICloneable" /> interface are
|
||||
/// cloned using that interface upon duplication; otherwise the item value or reference is
|
||||
/// simply copied.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Type of list element.</typeparam>
|
||||
public class GenericListAdaptor<T> : IReorderableListAdaptor
|
||||
{
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="GenericListAdaptor{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="list">The list which can be reordered.</param>
|
||||
/// <param name="itemDrawer">Callback to draw list item.</param>
|
||||
/// <param name="itemHeight">Height of list item in pixels.</param>
|
||||
public GenericListAdaptor(IList<T> list, ReorderableListControl.ItemDrawer<T> itemDrawer, float itemHeight)
|
||||
{
|
||||
_list = list;
|
||||
_itemDrawer = itemDrawer ?? ReorderableListGUI.DefaultItemDrawer;
|
||||
FixedItemHeight = itemHeight;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private IList<T> _list;
|
||||
|
||||
private ReorderableListControl.ItemDrawer<T> _itemDrawer;
|
||||
|
||||
/// <summary>
|
||||
/// Fixed height of each list item.
|
||||
/// </summary>
|
||||
public float FixedItemHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets element from list.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of element.</param>
|
||||
/// <returns>
|
||||
/// The element.
|
||||
/// </returns>
|
||||
public T this[int index] => _list[index];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying list data structure.
|
||||
/// </summary>
|
||||
public IList<T> List => _list;
|
||||
|
||||
#region IReorderableListAdaptor - Implementation
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _list.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanDrag(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanRemove(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Add()
|
||||
{
|
||||
_list.Add(default(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Insert(int index)
|
||||
{
|
||||
_list.Insert(index, default(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Duplicate(int index)
|
||||
{
|
||||
var newItem = _list[index];
|
||||
|
||||
var existingItem = newItem as ICloneable;
|
||||
if (existingItem != null)
|
||||
{
|
||||
newItem = (T)existingItem.Clone();
|
||||
}
|
||||
|
||||
_list.Insert(index + 1, newItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Remove(int index)
|
||||
{
|
||||
_list.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Move(int sourceIndex, int destIndex)
|
||||
{
|
||||
if (destIndex > sourceIndex)
|
||||
{
|
||||
--destIndex;
|
||||
}
|
||||
|
||||
var item = _list[sourceIndex];
|
||||
_list.RemoveAt(sourceIndex);
|
||||
_list.Insert(destIndex, item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Clear()
|
||||
{
|
||||
_list.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void BeginGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void EndGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItemBackground(Rect position, int index) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItem(Rect position, int index)
|
||||
{
|
||||
_list[index] = _itemDrawer(position, _list[index]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual float GetItemHeight(int index)
|
||||
{
|
||||
return FixedItemHeight;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Adaptor allowing reorderable list control to interface with list data.
|
||||
/// </summary>
|
||||
/// <see cref="IReorderableListDropTarget" />
|
||||
public interface IReorderableListAdaptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets count of elements in list.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an item can be reordered by dragging mouse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This should be a light-weight method since it will be used to determine
|
||||
/// whether grab handle should be included for each item in a reorderable list.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Please note that returning a value of <c>false</c> does not prevent movement
|
||||
/// on list item since other draggable items can be moved around it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="index">Zero-based index for list element.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if item can be dragged; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanDrag(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an item can be removed from list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This should be a light-weight method since it will be used to determine
|
||||
/// whether remove button should be included for each item in list.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is redundant when <see cref="ReorderableListFlags.HideRemoveButtons" />
|
||||
/// is specified.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="index">Zero-based index for list element.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if item can be removed; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanRemove(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Add new element at end of list.
|
||||
/// </summary>
|
||||
void Add();
|
||||
|
||||
/// <summary>
|
||||
/// Insert new element at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index for list element.</param>
|
||||
void Insert(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate existing element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Consider using the <see cref="System.ICloneable" /> interface to
|
||||
/// duplicate list elements where appropriate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="index">Zero-based index of list element.</param>
|
||||
void Duplicate(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Remove element at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of list element.</param>
|
||||
void Remove(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Move element from source index to destination index.
|
||||
/// </summary>
|
||||
/// <param name="sourceIndex">Zero-based index of source element.</param>
|
||||
/// <param name="destIndex">Zero-based index of destination element.</param>
|
||||
void Move(int sourceIndex, int destIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Clear all elements from list.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before any list items are drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is only used to handle GUI repaint events.</para>
|
||||
/// </remarks>
|
||||
/// <see cref="EndGUI()" />
|
||||
void BeginGUI();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after all list items have been drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is only used to handle GUI repaint events.</para>
|
||||
/// </remarks>
|
||||
/// <see cref="BeginGUI()" />
|
||||
void EndGUI();
|
||||
|
||||
/// <summary>
|
||||
/// Draws background of a list item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is only used to handle GUI repaint events.</para>
|
||||
/// <para>
|
||||
/// Background of list item spans a slightly larger area than the main
|
||||
/// interface that is drawn by <see cref="DrawItem(Rect, int)" /> since it is
|
||||
/// drawn behind the grab handle.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Total position of list element in GUI.</param>
|
||||
/// <param name="index">Zero-based index of array element.</param>
|
||||
void DrawItemBackground(Rect position, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Draws main interface for a list item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is used to handle all GUI events.</para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position in GUI.</param>
|
||||
/// <param name="index">Zero-based index of array element.</param>
|
||||
void DrawItem(Rect position, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets height of list item in pixels.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of array element.</param>
|
||||
/// <returns>
|
||||
/// Measurement in pixels.
|
||||
/// </returns>
|
||||
float GetItemHeight(int index);
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be implemented along with <see cref="IReorderableListAdaptor" /> when drop
|
||||
/// insertion or ordering is desired.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type of "drop" functionality can occur when the "drag" phase of the
|
||||
/// drag and drop operation was initiated elsewhere. For example, a custom
|
||||
/// <see cref="IReorderableListAdaptor" /> could insert entirely new items by
|
||||
/// dragging and dropping from the Unity "Project" window.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <see cref="IReorderableListAdaptor" />
|
||||
public interface IReorderableListDropTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether an item is being dragged and that it can be inserted
|
||||
/// or moved by dropping somewhere into the reorderable list control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is always called whilst drawing an editor GUI.</para>
|
||||
/// </remarks>
|
||||
/// <param name="insertionIndex">Zero-based index of insertion.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if item can be dropped; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
/// <see cref="UnityEditor.DragAndDrop" />
|
||||
bool CanDropInsert(int insertionIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the current drop insertion operation when <see cref="CanDropInsert(int)" />
|
||||
/// returns a value of <c>true</c> to process, accept or cancel.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is always called whilst drawing an editor GUI.</para>
|
||||
/// <para>
|
||||
/// This method is only called when <see cref="CanDropInsert(int)" />
|
||||
/// returns a value of <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="insertionIndex">Zero-based index of insertion.</param>
|
||||
/// <see cref="ReorderableListGUI.CurrentListControlID" />
|
||||
/// <see cref="UnityEditor.DragAndDrop" />
|
||||
void ProcessDropInsertion(int insertionIndex);
|
||||
}
|
||||
}
|
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functions to assist with GUIs.
|
||||
/// </summary>
|
||||
/// <exclude />
|
||||
public static class GUIHelper
|
||||
{
|
||||
static GUIHelper()
|
||||
{
|
||||
var tyGUIClip = typeof(GUIUtility).Assembly.GetType("UnityEngine.GUIClip", true);
|
||||
if (tyGUIClip != null)
|
||||
{
|
||||
var piVisibleRect = tyGUIClip.GetProperty("visibleRect", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (piVisibleRect != null)
|
||||
{
|
||||
VisibleRect = (Func<Rect>)Delegate.CreateDelegate(typeof(Func<Rect>), piVisibleRect.GetGetMethod(true));
|
||||
}
|
||||
}
|
||||
|
||||
var miFocusTextInControl = typeof(EditorGUI).GetMethod("FocusTextInControl", BindingFlags.Static | BindingFlags.Public);
|
||||
if (miFocusTextInControl == null)
|
||||
{
|
||||
miFocusTextInControl = typeof(GUI).GetMethod("FocusControl", BindingFlags.Static | BindingFlags.Public);
|
||||
}
|
||||
|
||||
FocusTextInControl = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), miFocusTextInControl);
|
||||
|
||||
s_SeparatorColor = EditorGUIUtility.isProSkin
|
||||
? new Color(0.11f, 0.11f, 0.11f)
|
||||
: new Color(0.5f, 0.5f, 0.5f);
|
||||
|
||||
s_SeparatorStyle = new GUIStyle();
|
||||
s_SeparatorStyle.normal.background = EditorGUIUtility.whiteTexture;
|
||||
s_SeparatorStyle.stretchWidth = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets visible rectangle within GUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>VisibleRect = TopmostRect + scrollViewOffsets</para>
|
||||
/// </remarks>
|
||||
public static Func<Rect> VisibleRect;
|
||||
|
||||
/// <summary>
|
||||
/// Focus control and text editor where applicable.
|
||||
/// </summary>
|
||||
public static Action<string> FocusTextInControl;
|
||||
|
||||
private static GUIStyle s_TempStyle = new GUIStyle();
|
||||
|
||||
private static GUIContent s_TempIconContent = new GUIContent();
|
||||
private static readonly int s_IconButtonHint = "_ReorderableIconButton_".GetHashCode();
|
||||
|
||||
private static readonly Color s_SeparatorColor;
|
||||
private static readonly GUIStyle s_SeparatorStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Draw texture using <see cref="GUIStyle" /> to workaround bug in Unity where
|
||||
/// <see cref="GUI.DrawTexture" /> flickers when embedded inside a property drawer.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of which to draw texture in space of GUI.</param>
|
||||
/// <param name="texture">Texture.</param>
|
||||
public static void DrawTexture(Rect position, Texture2D texture)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_TempStyle.normal.background = texture;
|
||||
|
||||
s_TempStyle.Draw(position, GUIContent.none, false, false, false, false);
|
||||
}
|
||||
|
||||
public static bool IconButton(Rect position, bool visible, Texture2D iconNormal, Texture2D iconActive, GUIStyle style)
|
||||
{
|
||||
var controlID = GUIUtility.GetControlID(s_IconButtonHint, FocusType.Passive);
|
||||
var result = false;
|
||||
|
||||
position.height += 1;
|
||||
|
||||
switch (Event.current.GetTypeForControl(controlID))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
// Do not allow button to be pressed using right mouse button since
|
||||
// context menu should be shown instead!
|
||||
if (GUI.enabled && Event.current.button != 1 && position.Contains(Event.current.mousePosition))
|
||||
{
|
||||
GUIUtility.hotControl = controlID;
|
||||
GUIUtility.keyboardControl = 0;
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
result = position.Contains(Event.current.mousePosition);
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
if (visible)
|
||||
{
|
||||
var isActive = GUIUtility.hotControl == controlID && position.Contains(Event.current.mousePosition);
|
||||
s_TempIconContent.image = isActive ? iconActive : iconNormal;
|
||||
position.height -= 1;
|
||||
style.Draw(position, s_TempIconContent, isActive, isActive, false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IconButton(Rect position, Texture2D iconNormal, Texture2D iconActive, GUIStyle style)
|
||||
{
|
||||
return IconButton(position, true, iconNormal, iconActive, style);
|
||||
}
|
||||
|
||||
public static void Separator(Rect position, Color color)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
var restoreColor = GUI.color;
|
||||
GUI.color = color;
|
||||
s_SeparatorStyle.Draw(position, false, false, false, false);
|
||||
GUI.color = restoreColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Separator(Rect position)
|
||||
{
|
||||
Separator(position, s_SeparatorColor);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Internal
|
||||
{
|
||||
/// <exclude />
|
||||
public enum ReorderableListTexture
|
||||
{
|
||||
Icon_Add_Normal = 0,
|
||||
Icon_Add_Active,
|
||||
Icon_AddMenu_Normal,
|
||||
Icon_AddMenu_Active,
|
||||
Icon_Menu_Normal,
|
||||
Icon_Menu_Active,
|
||||
Icon_Remove_Normal,
|
||||
Icon_Remove_Active,
|
||||
Button_Normal,
|
||||
Button_Active,
|
||||
Button2_Normal,
|
||||
Button2_Active,
|
||||
TitleBackground,
|
||||
ContainerBackground,
|
||||
Container2Background,
|
||||
GrabHandle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resources to assist with reorderable list control.
|
||||
/// </summary>
|
||||
/// <exclude />
|
||||
public static class ReorderableListResources
|
||||
{
|
||||
static ReorderableListResources()
|
||||
{
|
||||
GenerateSpecialTextures();
|
||||
LoadResourceAssets();
|
||||
}
|
||||
|
||||
#region Texture Resources
|
||||
|
||||
/// <summary>
|
||||
/// Resource assets for light skin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Resource assets are PNG images which have been encoded using a base-64
|
||||
/// string so that actual asset files are not necessary.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static string[] s_LightSkin =
|
||||
{
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACxJREFUeNpi/P//PwMM6OvrgzkXL15khIkxMRAABBUw6unp/afMBNo7EiDAAEKeD5EsXZcTAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1JREFUeNpi/P//PwMM3L17F8xRVlZmhIkxMRAABBUw3rlz5z9lJtDekQABBgCvqxGbQWpEqwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAICAYAAAD9aA/QAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAERJREFUeNpi/P//PwMxQF9fH6zw4sWLjMSoZ2KgEaCZwYz4ggLmfVwAX7AMjIuJjTxsPqOKi9EtA/GpFhQww2E0QIABAPF5IGHNU7adAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAICAYAAAD9aA/QAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAERJREFUeNpi/P//PwMx4O7du2CFysrKjMSoZ2KgEaCZwYz4ggLmfVwAX7AMjIuJjTxsPqOKi9EtA/GpFhQww2E0QIABACBuGkOOEiPJAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADBJREFUeNpi/P//PwM6YGLAAigUZNHX18ewienixYuMyAJgPshJIKynp/cfxgYIMACCMhb+oVNPwwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADFJREFUeNpi/P//PwM6YGLAAigUZLl79y6GTUzKysqMyAJgPshJIHznzp3/MDZAgAEAkoIW/jHg7H4AAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABVJREFUeNpiVFZWZsAGmBhwAIAAAwAURgBt4C03ZwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABVJREFUeNpivHPnDgM2wMSAAwAEGAB8VgKYlvqkBwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiKCoq+v/p06f/ly9fhmMQHyTOxIAH4JVkARHv379nkJeXhwuC+CDA+P//f4bi4uL/6Lp6e3sZAQIMACmoI7rWhl0KAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiFBER+f/jxw8GNjY2Bhj49esXAwcHBwMTAx6AV5IFRPz58wdFEMZn/P//P4OoqOh/dF2vX79mBAgwADpeFCsbeaC+AAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFBJREFUeNpi/P//P0NxcfF/BjTQ29vLyFBUVPT/4cOH/z99+gTHID5InAWkSlBQkAEoANclLy8PppkY8AC8kmBj379/DzcKxgcBRnyuBQgwACVNLqBePwzmAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAElJREFUeNp8jjEKADEIBNdDrCz1/w+0tRQMOchxpHC6dVhW6m64e+MiIojMrDMTzPyJqoKq4r1sISJ3GQ8GRsln48/JNH27BBgAUhQbSyMxqzEAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpi/P//P0NxcfF/BgRgZP78+fN/VVVVhpCQEAZjY2OGs2fPNrCApBwdHRkePHgAVwoWnDVrFgMyAAgwAAt4E1dCq1obAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADtJREFUeNpi/P//P0NxcfF/Bijo7e1lZCgqKvr/6dOn/5cvXwbTID4TSPb9+/cM8vLyYBoEGLFpBwgwAHGiI8KoD3BZAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAIAAADJUWIXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpiDA0NZUACLEDc2dkJ4ZSXlzMxoAJGNPUAAQYAwbcFBwYygqkAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACdJREFUeNpi/PTp038GAoClvr6ekBoGxv//CRrEwPL582fqWAcQYAAnaA2zsd+RkQAAAABJRU5ErkJggg==",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Resource assets for dark skin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Resource assets are PNG images which have been encoded using a base-64
|
||||
/// string so that actual asset files are not necessary.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static string[] s_DarkSkin =
|
||||
{
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE9JREFUeNpi/P//PwM+wITMOXr06H8QxqmAoAnYAOORI0f+U2aCsrIy3ISFCxeC6fj4eIQCZG/CfGBtbc1IvBXIJqioqIA5d+7cgZsAEGAAsHYfVsuw0XYAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEZJREFUeNpi/P//PwM+wITM+Q8FOBUQNAEbYPmPxRHIYoRN4OLignO+ffsGppHFGJFtgBnNCATEW4HMgRn9/ft3uBhAgAEAbZ0gJEmOtOAAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAKCAYAAACwoK7bAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAG1JREFUeNpi/P//PwMtABOxCo8ePfofhKluMM1cTCpgxBfGhLxubW3NOLhcrKKiApdcuHAhmI6Pj4fL37lzhxGXzxiJTW4wzdi8D3IAzGKY5VQJCpDLYT4B0WCfgFxMDFZWVv4PwoTUwNgAAQYA7Mltu4fEN4wAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAKCAYAAACwoK7bAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGVJREFUeNpi/P//PwMtABOxCv9DAdUNppmLSQWM+HxHyOuMQEB3F7Pgk+Ti4oKzv337hiH2/ft3nD5jJDaiYZqxeZ+Tk/M/zGKY5VQJCqDLGWE+AdEgPtEuBrkKZgg+NTB5gAADAJGHOCAbby7zAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADpJREFUeNpi/P//PwM6YGLAAmghyHL06FEM65ni4+NRBMB8kDuVlZX/Hzly5D+IBrsbRMAkYGyAAAMAB7YiCOfAQ0cAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADdJREFUeNpi/P//PwM6YGLAAmghyPIfi/VMXFxcKAJgPkghBwfH/3///v0H0WCNIAImAWMDBBgA09Igc2M/ueMAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAYAAACzzX7wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi/P//PwM+wHL06FG8KpgYCABGZWVlvCYABBgA7/sHvGw+cz8AAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAYAAACzzX7wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi/P//PwM+wPKfgAomBgKAhYuLC68CgAADAAxjByOjCHIRAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAERJREFUeNpiVFZW/u/i4sLw4sULBhiQkJBg2LNnDwMTAx6AV5IFRLx9+xZsFAyA+CDA+P//fwYVFZX/6Lru3LnDCBBgAEqlFEYRrf2nAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiFBER+f/jxw8GNjY2Bhj49esXAwcHBwMTAx6AV5IFRPz58wdFEMZn/P//P4OoqOh/dF2vX79mBAgwADpeFCsbeaC+AAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAExJREFUeNpi/P//P4OKisp/BjRw584dRhaQhKGhIYOwsDBc4u3bt2ANLCAOSOLFixdwSQkJCTDNxIAH4JVkgdkBMwrGBwFGfK4FCDAAV1AdhemEguIAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAElJREFUeNp8jjEKADEIBNdDrCz1/w+0tRQMOchxpHC6dVhW6m64e+MiIojMrDMTzPyJqoKq4r1sISJ3GQ8GRsln48/JNH27BBgAUhQbSyMxqzEAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADtJREFUeNpi/P//P4OKisp/Bii4c+cOIwtIQE9Pj+HLly9gQRCfBcQACbx69QqmmAEseO/ePQZkABBgAD04FXsmmijSAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpi/P//P4OKisp/Bii4c+cOIwtIwMXFheHFixcMEhISYAVMINm3b9+CBUA0CDCiazc0NGQECDAAdH0YelA27kgAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACZJREFUeNpi/P//vxQDGmABEffv3/8ME1BUVORlYsACGLFpBwgwABaWCjfQEetnAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACRJREFUeNpizM3N/c9AADAqKysTVMTi5eXFSFAREFPHOoAAAwBCfwcAO8g48QAAAABJRU5ErkJggg==",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets light or dark version of the specified texture.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D GetTexture(ReorderableListTexture name)
|
||||
{
|
||||
return s_Cached[(int)name];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generated Resources
|
||||
|
||||
public static Texture2D texHighlightColor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate special textures.
|
||||
/// </summary>
|
||||
private static void GenerateSpecialTextures()
|
||||
{
|
||||
texHighlightColor = CreatePixelTexture("(Generated) Highlight Color", ReorderableListStyles.SelectionBackgroundColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create 1x1 pixel texture of specified color.
|
||||
/// </summary>
|
||||
/// <param name="name">Name for texture object.</param>
|
||||
/// <param name="color">Pixel color.</param>
|
||||
/// <returns>
|
||||
/// The new <c>Texture2D</c> instance.
|
||||
/// </returns>
|
||||
public static Texture2D CreatePixelTexture(string name, Color color)
|
||||
{
|
||||
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false, LudiqGUIUtility.createLinearTextures);
|
||||
tex.name = name;
|
||||
tex.hideFlags = HideFlags.HideAndDontSave;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.SetPixel(0, 0, color);
|
||||
tex.Apply();
|
||||
return tex;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Load PNG from Base-64 Encoded String
|
||||
|
||||
private static Texture2D[] s_Cached;
|
||||
|
||||
/// <summary>
|
||||
/// Read textures from base-64 encoded strings. Automatically selects assets based
|
||||
/// upon whether the light or dark (pro) skin is active.
|
||||
/// </summary>
|
||||
private static void LoadResourceAssets()
|
||||
{
|
||||
var skin = EditorGUIUtility.isProSkin ? s_DarkSkin : s_LightSkin;
|
||||
s_Cached = new Texture2D[skin.Length];
|
||||
|
||||
for (var i = 0; i < s_Cached.Length; ++i)
|
||||
{
|
||||
// Get image data (PNG) from base64 encoded strings.
|
||||
var imageData = Convert.FromBase64String(skin[i]);
|
||||
|
||||
// Gather image size from image data.
|
||||
int texWidth, texHeight;
|
||||
GetImageSize(imageData, out texWidth, out texHeight);
|
||||
|
||||
// Generate texture asset.
|
||||
var tex = new Texture2D(texWidth, texHeight, TextureFormat.ARGB32, false, LudiqGUIUtility.createLinearTextures);
|
||||
tex.hideFlags = HideFlags.HideAndDontSave;
|
||||
tex.name = "(Generated) ReorderableList:" + i;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.LoadImage(imageData);
|
||||
|
||||
s_Cached[i] = tex;
|
||||
}
|
||||
|
||||
s_LightSkin = null;
|
||||
s_DarkSkin = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read width and height if PNG file in pixels.
|
||||
/// </summary>
|
||||
/// <param name="imageData">PNG image data.</param>
|
||||
/// <param name="width">Width of image in pixels.</param>
|
||||
/// <param name="height">Height of image in pixels.</param>
|
||||
private static void GetImageSize(byte[] imageData, out int width, out int height)
|
||||
{
|
||||
width = ReadInt(imageData, 3 + 15);
|
||||
height = ReadInt(imageData, 3 + 15 + 2 + 2);
|
||||
}
|
||||
|
||||
private static int ReadInt(byte[] imageData, int offset)
|
||||
{
|
||||
return (imageData[offset] << 8) | imageData[offset + 1];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functionality for <see cref="SerializedPropertyAdaptor" /> implementations.
|
||||
/// </summary>
|
||||
public static class SerializedPropertyUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Reset the value of a property.
|
||||
/// </summary>
|
||||
/// <param name="property">Serialized property for a serialized property.</param>
|
||||
public static void ResetValue(SerializedProperty property)
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(property));
|
||||
}
|
||||
|
||||
switch (property.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Integer:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
property.boolValue = false;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
property.floatValue = 0f;
|
||||
break;
|
||||
case SerializedPropertyType.String:
|
||||
property.stringValue = "";
|
||||
break;
|
||||
case SerializedPropertyType.Color:
|
||||
property.colorValue = Color.black;
|
||||
break;
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
property.objectReferenceValue = null;
|
||||
break;
|
||||
case SerializedPropertyType.LayerMask:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
property.enumValueIndex = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
property.vector2Value = default(Vector2);
|
||||
break;
|
||||
case SerializedPropertyType.Vector3:
|
||||
property.vector3Value = default(Vector3);
|
||||
break;
|
||||
case SerializedPropertyType.Vector4:
|
||||
property.vector4Value = default(Vector4);
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
property.rectValue = default(Rect);
|
||||
break;
|
||||
case SerializedPropertyType.ArraySize:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
property.animationCurveValue = AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
break;
|
||||
case SerializedPropertyType.Bounds:
|
||||
property.boundsValue = default(Bounds);
|
||||
break;
|
||||
case SerializedPropertyType.Gradient:
|
||||
//!TODO: Amend when Unity add a public API for setting the gradient.
|
||||
break;
|
||||
}
|
||||
|
||||
if (property.isArray)
|
||||
{
|
||||
property.arraySize = 0;
|
||||
}
|
||||
|
||||
ResetChildPropertyValues(property);
|
||||
}
|
||||
|
||||
private static void ResetChildPropertyValues(SerializedProperty element)
|
||||
{
|
||||
if (!element.hasChildren)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var childProperty = element.Copy();
|
||||
var elementPropertyDepth = element.depth;
|
||||
var enterChildren = true;
|
||||
|
||||
while (childProperty.Next(enterChildren) && childProperty.depth > elementPropertyDepth)
|
||||
{
|
||||
enterChildren = false;
|
||||
ResetValue(childProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies value of <paramref name="sourceProperty" /> into <pararef name="destProperty" />.
|
||||
/// </summary>
|
||||
/// <param name="destProperty">Destination property.</param>
|
||||
/// <param name="sourceProperty">Source property.</param>
|
||||
public static void CopyPropertyValue(SerializedProperty destProperty, SerializedProperty sourceProperty)
|
||||
{
|
||||
if (destProperty == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(destProperty));
|
||||
}
|
||||
if (sourceProperty == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceProperty));
|
||||
}
|
||||
|
||||
sourceProperty = sourceProperty.Copy();
|
||||
destProperty = destProperty.Copy();
|
||||
|
||||
CopyPropertyValueSingular(destProperty, sourceProperty);
|
||||
|
||||
if (sourceProperty.hasChildren)
|
||||
{
|
||||
var elementPropertyDepth = sourceProperty.depth;
|
||||
while (sourceProperty.Next(true) && destProperty.Next(true) && sourceProperty.depth > elementPropertyDepth)
|
||||
{
|
||||
CopyPropertyValueSingular(destProperty, sourceProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyPropertyValueSingular(SerializedProperty destProperty, SerializedProperty sourceProperty)
|
||||
{
|
||||
switch (destProperty.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Integer:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
destProperty.boolValue = sourceProperty.boolValue;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
destProperty.floatValue = sourceProperty.floatValue;
|
||||
break;
|
||||
case SerializedPropertyType.String:
|
||||
destProperty.stringValue = sourceProperty.stringValue;
|
||||
break;
|
||||
case SerializedPropertyType.Color:
|
||||
destProperty.colorValue = sourceProperty.colorValue;
|
||||
break;
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
destProperty.objectReferenceValue = sourceProperty.objectReferenceValue;
|
||||
break;
|
||||
case SerializedPropertyType.LayerMask:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
destProperty.enumValueIndex = sourceProperty.enumValueIndex;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
destProperty.vector2Value = sourceProperty.vector2Value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector3:
|
||||
destProperty.vector3Value = sourceProperty.vector3Value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector4:
|
||||
destProperty.vector4Value = sourceProperty.vector4Value;
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
destProperty.rectValue = sourceProperty.rectValue;
|
||||
break;
|
||||
case SerializedPropertyType.ArraySize:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
destProperty.animationCurveValue = sourceProperty.animationCurveValue;
|
||||
break;
|
||||
case SerializedPropertyType.Bounds:
|
||||
destProperty.boundsValue = sourceProperty.boundsValue;
|
||||
break;
|
||||
case SerializedPropertyType.Gradient:
|
||||
//!TODO: Amend when Unity add a public API for setting the gradient.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="AddMenuClickedEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class AddMenuClickedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemMovedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="buttonPosition">Position of the add menu button.</param>
|
||||
public AddMenuClickedEventArgs(IReorderableListAdaptor adaptor, Rect buttonPosition)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ButtonPosition = buttonPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the add menu button.
|
||||
/// </summary>
|
||||
public Rect ButtonPosition { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked when the "Add Menu" button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void AddMenuClickedEventHandler(object sender, AddMenuClickedEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemInsertedEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemInsertedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemInsertedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="itemIndex">Zero-based index of item.</param>
|
||||
/// <param name="wasDuplicated">Indicates if inserted item was duplicated from another item.</param>
|
||||
public ItemInsertedEventArgs(IReorderableListAdaptor adaptor, int itemIndex, bool wasDuplicated)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ItemIndex = itemIndex;
|
||||
WasDuplicated = wasDuplicated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets zero-based index of item which was inserted.
|
||||
/// </summary>
|
||||
public int ItemIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if inserted item was duplicated from another item.
|
||||
/// </summary>
|
||||
public bool WasDuplicated { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked after new list item is inserted.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemInsertedEventHandler(object sender, ItemInsertedEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemRemovingEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemRemovingEventArgs : CancelEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemRemovingEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="itemIndex">Zero-based index of item.</param>
|
||||
public ItemRemovingEventArgs(IReorderableListAdaptor adaptor, int itemIndex)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ItemIndex = itemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets zero-based index of item which is being removed.
|
||||
/// </summary>
|
||||
public int ItemIndex { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked before a list item is removed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Item removal can be cancelled by setting <see cref="CancelEventArgs.Cancel" />
|
||||
/// to <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemRemovingEventHandler(object sender, ItemRemovingEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemMovingEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemMovingEventArgs : CancelEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemMovingEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="itemIndex">Zero-based index of item.</param>
|
||||
/// <param name="destinationItemIndex">Xero-based index of item destination.</param>
|
||||
public ItemMovingEventArgs(IReorderableListAdaptor adaptor, int itemIndex, int destinationItemIndex)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ItemIndex = itemIndex;
|
||||
DestinationItemIndex = destinationItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets current zero-based index of item which is going to be moved.
|
||||
/// </summary>
|
||||
public int ItemIndex { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the new candidate zero-based index for the item.
|
||||
/// </summary>
|
||||
/// <seealso cref="NewItemIndex" />
|
||||
public int DestinationItemIndex { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets zero-based index of item <strong>after</strong> it has been moved.
|
||||
/// </summary>
|
||||
/// <seealso cref="DestinationItemIndex" />
|
||||
public int NewItemIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = DestinationItemIndex;
|
||||
if (result > ItemIndex)
|
||||
{
|
||||
--result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked before a list item is moved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Moving of item can be cancelled by setting <see cref="CancelEventArgs.Cancel" />
|
||||
/// to <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemMovingEventHandler(object sender, ItemMovingEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemMovedEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemMovedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemMovedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="oldItemIndex">Old zero-based index of item.</param>
|
||||
/// <param name="newItemIndex">New zero-based index of item.</param>
|
||||
public ItemMovedEventArgs(IReorderableListAdaptor adaptor, int oldItemIndex, int newItemIndex)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
OldItemIndex = oldItemIndex;
|
||||
NewItemIndex = newItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets old zero-based index of the item which was moved.
|
||||
/// </summary>
|
||||
public int OldItemIndex { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets new zero-based index of the item which was moved.
|
||||
/// </summary>
|
||||
public int NewItemIndex { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked after a list item is moved.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemMovedEventHandler(object sender, ItemMovedEventArgs args);
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional flags which can be passed into reorderable list field.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <para>Multiple flags can be specified if desired:</para>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// var flags = ReorderableListFlags.HideAddButton | ReorderableListFlags.HideRemoveButtons;
|
||||
/// ReorderableListGUI.ListField(list, flags);
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
[Flags]
|
||||
public enum ReorderableListFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Hide grab handles and disable reordering of list items.
|
||||
/// </summary>
|
||||
DisableReordering = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Hide add button at base of control.
|
||||
/// </summary>
|
||||
HideAddButton = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Hide remove buttons from list items.
|
||||
/// </summary>
|
||||
HideRemoveButtons = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// Do not display context menu upon right-clicking grab handle.
|
||||
/// </summary>
|
||||
DisableContextMenu = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// Hide "Duplicate" option from context menu.
|
||||
/// </summary>
|
||||
DisableDuplicateCommand = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// Do not automatically focus first control of newly added items.
|
||||
/// </summary>
|
||||
DisableAutoFocus = 0x0020,
|
||||
|
||||
/// <summary>
|
||||
/// Show zero-based index of array elements.
|
||||
/// </summary>
|
||||
ShowIndices = 0x0040,
|
||||
|
||||
/// <exclude />
|
||||
[Obsolete("This flag is redundant because the clipping optimization was removed.")]
|
||||
DisableClipping = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// Do not attempt to automatically scroll when list is inside a scroll view and
|
||||
/// the mouse pointer is dragged outside of the visible portion of the list.
|
||||
/// </summary>
|
||||
DisableAutoScroll = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// Show "Size" field at base of list control.
|
||||
/// </summary>
|
||||
ShowSizeField = 0x0200,
|
||||
}
|
||||
}
|
@@ -0,0 +1,681 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for drawing reorderable lists.
|
||||
/// </summary>
|
||||
public static class ReorderableListGUI
|
||||
{
|
||||
static ReorderableListGUI()
|
||||
{
|
||||
DefaultListControl = new ReorderableListControl();
|
||||
|
||||
// Duplicate default styles to prevent user scripts from interferring with
|
||||
// the default list control instance.
|
||||
DefaultListControl.ContainerStyle = new GUIStyle(ReorderableListStyles.Container);
|
||||
DefaultListControl.FooterButtonStyle = new GUIStyle(ReorderableListStyles.FooterButton);
|
||||
DefaultListControl.ItemButtonStyle = new GUIStyle(ReorderableListStyles.ItemButton);
|
||||
|
||||
IndexOfChangedItem = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default list item height is 18 pixels.
|
||||
/// </summary>
|
||||
public const float DefaultItemHeight = 18;
|
||||
|
||||
private static GUIContent s_Temp = new GUIContent();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the zero-based index of the last item that was changed. A value of -1
|
||||
/// indicates that no item was changed by list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This property should not be set when items are added or removed.</para>
|
||||
/// </remarks>
|
||||
public static int IndexOfChangedItem { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control ID of the list that is currently being drawn.
|
||||
/// </summary>
|
||||
public static int CurrentListControlID => ReorderableListControl.CurrentListControlID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the list control that is currently being drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value of this property should be ignored for <see cref="EventType.Layout" />
|
||||
/// type events when using reorderable list controls with automatic layout.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <see cref="CurrentItemTotalPosition" />
|
||||
public static Rect CurrentListPosition => ReorderableListControl.CurrentListPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the zero-based index of the list item that is currently being drawn;
|
||||
/// or a value of -1 if no item is currently being drawn.
|
||||
/// </summary>
|
||||
public static int CurrentItemIndex => ReorderableListControl.CurrentItemIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total position of the list item that is currently being drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value of this property should be ignored for <see cref="EventType.Layout" />
|
||||
/// type events when using reorderable list controls with automatic layout.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <see cref="CurrentItemIndex" />
|
||||
/// <see cref="CurrentListPosition" />
|
||||
public static Rect CurrentItemTotalPosition => ReorderableListControl.CurrentItemTotalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default list control implementation.
|
||||
/// </summary>
|
||||
private static ReorderableListControl DefaultListControl { get; set; }
|
||||
|
||||
#region Basic Item Drawers
|
||||
|
||||
/// <summary>
|
||||
/// Default list item drawer implementation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Always presents the label "Item drawer not implemented.".</para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position to draw list item control(s).</param>
|
||||
/// <param name="item">Value of list item.</param>
|
||||
/// <returns>
|
||||
/// Unmodified value of list item.
|
||||
/// </returns>
|
||||
/// <typeparam name="T">Type of list item.</typeparam>
|
||||
public static T DefaultItemDrawer<T>(Rect position, T item)
|
||||
{
|
||||
GUI.Label(position, "Item drawer not implemented.");
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws text field allowing list items to be edited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Null values are automatically changed to empty strings since null
|
||||
/// values cannot be edited using a text field.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Value of <c>GUI.changed</c> is set to <c>true</c> if value of item
|
||||
/// is modified.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position to draw list item control(s).</param>
|
||||
/// <param name="item">Value of list item.</param>
|
||||
/// <returns>
|
||||
/// Modified value of list item.
|
||||
/// </returns>
|
||||
public static string TextFieldItemDrawer(Rect position, string item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
item = "";
|
||||
GUI.changed = true;
|
||||
}
|
||||
return EditorGUI.TextField(position, item);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Title Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When needed, should be shown immediately before list field.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// ReorderableListGUI.Title(titleContent);
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// ReorderableListGUI.Title(titleContent);
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
/// <param name="title">Content for title control.</param>
|
||||
public static void Title(GUIContent title)
|
||||
{
|
||||
var position = GUILayoutUtility.GetRect(title, ReorderableListStyles.Title);
|
||||
Title(position, title);
|
||||
LudiqGUI.Space(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When needed, should be shown immediately before list field.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// ReorderableListGUI.Title("Your Title");
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// ReorderableListGUI.Title('Your Title');
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
/// <param name="title">Text for title control.</param>
|
||||
public static void Title(string title)
|
||||
{
|
||||
s_Temp.text = title;
|
||||
Title(s_Temp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field with absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="title">Content for title control.</param>
|
||||
public static void Title(Rect position, GUIContent title)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
ReorderableListStyles.Title.Draw(position, title, false, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field with absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="text">Text for title control.</param>
|
||||
public static void Title(Rect position, string text)
|
||||
{
|
||||
s_Temp.text = text;
|
||||
Title(position, s_Temp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List<T> Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control.
|
||||
/// </summary>
|
||||
/// <param name="list">The list which can be reordered.</param>
|
||||
/// <param name="drawItem">Callback to draw list item.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="itemHeight">Height of a single list item.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <typeparam name="T">Type of list item.</typeparam>
|
||||
private static void DoListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new GenericListAdaptor<T>(list, drawItem, itemHeight);
|
||||
ReorderableListControl.DrawControlFromState(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control with absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="list">The list which can be reordered.</param>
|
||||
/// <param name="drawItem">Callback to draw list item.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="itemHeight">Height of a single list item.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <typeparam name="T">Type of list item.</typeparam>
|
||||
private static void DoListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new GenericListAdaptor<T>(list, drawItem, itemHeight);
|
||||
ReorderableListControl.DrawControlFromState(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, float itemHeight)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, float itemHeight)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, null, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight)
|
||||
{
|
||||
DoListField(list, drawItem, null, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, null, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem)
|
||||
{
|
||||
DoListField(list, drawItem, null, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate height of list field for absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="itemCount">Count of items in list.</param>
|
||||
/// <param name="itemHeight">Fixed height of list item.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <returns>
|
||||
/// Required list height in pixels.
|
||||
/// </returns>
|
||||
public static float CalculateListFieldHeight(int itemCount, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
// We need to push/pop flags so that nested controls are properly calculated.
|
||||
var restoreFlags = DefaultListControl.Flags;
|
||||
try
|
||||
{
|
||||
DefaultListControl.Flags = flags;
|
||||
return DefaultListControl.CalculateListHeight(itemCount, itemHeight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DefaultListControl.Flags = restoreFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(int, float, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(int itemCount, ReorderableListFlags flags)
|
||||
{
|
||||
return CalculateListFieldHeight(itemCount, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(int, float, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(int itemCount, float itemHeight)
|
||||
{
|
||||
return CalculateListFieldHeight(itemCount, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(int, float, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(int itemCount)
|
||||
{
|
||||
return CalculateListFieldHeight(itemCount, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SerializedProperty Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for serializable property array.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serializable property.</param>
|
||||
/// <param name="fixedItemHeight">
|
||||
/// Use fixed height for items rather than
|
||||
/// <see cref="UnityEditor.EditorGUI.GetPropertyHeight(SerializedProperty)" />.
|
||||
/// </param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new SerializedPropertyAdaptor(arrayProperty, fixedItemHeight);
|
||||
ReorderableListControl.DrawControlFromState(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for serializable property array.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="arrayProperty">Serializable property.</param>
|
||||
/// <param name="fixedItemHeight">
|
||||
/// Use fixed height for items rather than
|
||||
/// <see cref="UnityEditor.EditorGUI.GetPropertyHeight(SerializedProperty)" />.
|
||||
/// </param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new SerializedPropertyAdaptor(arrayProperty, fixedItemHeight);
|
||||
ReorderableListControl.DrawControlFromState(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, 0, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(arrayProperty, 0, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, 0, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty)
|
||||
{
|
||||
DoListField(arrayProperty, 0, null, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, null, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate height of list field for absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serializable property.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <returns>
|
||||
/// Required list height in pixels.
|
||||
/// </returns>
|
||||
public static float CalculateListFieldHeight(SerializedProperty arrayProperty, ReorderableListFlags flags)
|
||||
{
|
||||
// We need to push/pop flags so that nested controls are properly calculated.
|
||||
var restoreFlags = DefaultListControl.Flags;
|
||||
try
|
||||
{
|
||||
DefaultListControl.Flags = flags;
|
||||
return DefaultListControl.CalculateListHeight(new SerializedPropertyAdaptor(arrayProperty));
|
||||
}
|
||||
finally
|
||||
{
|
||||
DefaultListControl.Flags = restoreFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(SerializedProperty, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(SerializedProperty arrayProperty)
|
||||
{
|
||||
return CalculateListFieldHeight(arrayProperty, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SerializedProperty Control (Fixed Item Height)
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, null, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, null, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Adaptor Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for adapted collection.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListField(IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags = 0)
|
||||
{
|
||||
ReorderableListControl.DrawControlFromState(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for adapted collection.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags = 0)
|
||||
{
|
||||
ReorderableListControl.DrawControlFromState(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(adaptor, drawEmpty);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, drawEmpty);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(adaptor, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor)
|
||||
{
|
||||
DoListField(adaptor, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate height of list field for adapted collection.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <returns>
|
||||
/// Required list height in pixels.
|
||||
/// </returns>
|
||||
public static float CalculateListFieldHeight(IReorderableListAdaptor adaptor, ReorderableListFlags flags)
|
||||
{
|
||||
// We need to push/pop flags so that nested controls are properly calculated.
|
||||
var restoreFlags = DefaultListControl.Flags;
|
||||
try
|
||||
{
|
||||
DefaultListControl.Flags = flags;
|
||||
return DefaultListControl.CalculateListHeight(adaptor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DefaultListControl.Flags = restoreFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(IReorderableListAdaptor, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(IReorderableListAdaptor adaptor)
|
||||
{
|
||||
return CalculateListFieldHeight(adaptor, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using Unity.VisualScripting.ReorderableList.Internal;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Styles for the <see cref="ReorderableListControl" />.
|
||||
/// </summary>
|
||||
public static class ReorderableListStyles
|
||||
{
|
||||
static ReorderableListStyles()
|
||||
{
|
||||
Title = new GUIStyle();
|
||||
Title.border = new RectOffset(2, 2, 2, 1);
|
||||
Title.margin = new RectOffset(5, 5, 5, 0);
|
||||
Title.padding = new RectOffset(5, 5, 3, 3);
|
||||
Title.alignment = TextAnchor.MiddleLeft;
|
||||
Title.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.TitleBackground);
|
||||
Title.normal.textColor = EditorGUIUtility.isProSkin
|
||||
? new Color(0.8f, 0.8f, 0.8f)
|
||||
: new Color(0.2f, 0.2f, 0.2f);
|
||||
|
||||
Container = new GUIStyle();
|
||||
Container.border = new RectOffset(2, 2, 2, 2);
|
||||
Container.margin = new RectOffset(5, 5, 0, 0);
|
||||
Container.padding = new RectOffset(2, 2, 2, 2);
|
||||
Container.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.ContainerBackground);
|
||||
|
||||
Container2 = new GUIStyle(Container);
|
||||
Container2.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.Container2Background);
|
||||
|
||||
FooterButton = new GUIStyle();
|
||||
FooterButton.fixedHeight = 18;
|
||||
FooterButton.alignment = TextAnchor.MiddleCenter;
|
||||
FooterButton.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button_Normal);
|
||||
FooterButton.active.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button_Active);
|
||||
FooterButton.border = new RectOffset(3, 3, 1, 3);
|
||||
FooterButton.padding = new RectOffset(2, 2, 0, 2);
|
||||
FooterButton.clipping = TextClipping.Overflow;
|
||||
|
||||
FooterButton2 = new GUIStyle();
|
||||
FooterButton2.fixedHeight = 18;
|
||||
FooterButton2.alignment = TextAnchor.MiddleCenter;
|
||||
FooterButton2.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button2_Normal);
|
||||
FooterButton2.active.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button2_Active);
|
||||
FooterButton2.border = new RectOffset(3, 3, 3, 3);
|
||||
FooterButton2.padding = new RectOffset(2, 2, 2, 2);
|
||||
FooterButton2.clipping = TextClipping.Overflow;
|
||||
|
||||
ItemButton = new GUIStyle();
|
||||
ItemButton.active.background = ReorderableListResources.CreatePixelTexture("Dark Pixel (List GUI)", new Color32(18, 18, 18, 255));
|
||||
ItemButton.imagePosition = ImagePosition.ImageOnly;
|
||||
ItemButton.alignment = TextAnchor.MiddleCenter;
|
||||
ItemButton.overflow = new RectOffset(0, 0, -1, 0);
|
||||
ItemButton.padding = new RectOffset(0, 0, 1, 0);
|
||||
ItemButton.contentOffset = new Vector2(0, -1f);
|
||||
|
||||
SelectedItem = new GUIStyle();
|
||||
SelectedItem.normal.background = ReorderableListResources.texHighlightColor;
|
||||
SelectedItem.normal.textColor = Color.white;
|
||||
SelectedItem.fontSize = 12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for title header.
|
||||
/// </summary>
|
||||
public static GUIStyle Title { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for the background of list control.
|
||||
/// </summary>
|
||||
public static GUIStyle Container { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternative style for the background of list control.
|
||||
/// </summary>
|
||||
public static GUIStyle Container2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for footer button.
|
||||
/// </summary>
|
||||
public static GUIStyle FooterButton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternative style for footer button.
|
||||
/// </summary>
|
||||
public static GUIStyle FooterButton2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for remove item button.
|
||||
/// </summary>
|
||||
public static GUIStyle ItemButton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for the background of a selected item.
|
||||
/// </summary>
|
||||
public static GUIStyle SelectedItem { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets color for the horizontal lines that appear between list items.
|
||||
/// </summary>
|
||||
public static Color HorizontalLineColor => EditorGUIUtility.isProSkin ? new Color(1f, 1f, 1f, 0.14f) : new Color(0.59f, 0.59f, 0.59f, 0.55f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets color of background for a selected list item.
|
||||
/// </summary>
|
||||
public static Color SelectionBackgroundColor => EditorGUIUtility.isProSkin ? new Color32(62, 95, 150, 255) : new Color32(62, 125, 231, 255);
|
||||
}
|
||||
}
|
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Reorderable list adaptor for serialized array property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This adaptor can be subclassed to add special logic to item height calculation.
|
||||
/// You may want to implement a custom adaptor class where specialised functionality
|
||||
/// is needed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// List elements are <b>not</b> cloned using the <see cref="System.ICloneable" />
|
||||
/// interface when using a <see cref="UnityEditor.SerializedProperty" /> to
|
||||
/// manipulate lists.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class SerializedPropertyAdaptor : IReorderableListAdaptor
|
||||
{
|
||||
private SerializedProperty _arrayProperty;
|
||||
|
||||
/// <summary>
|
||||
/// Fixed height of each list item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Non-zero value overrides property drawer height calculation
|
||||
/// which is more efficient.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public float FixedItemHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets element from list.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of element.</param>
|
||||
/// <returns>
|
||||
/// Serialized property wrapper for array element.
|
||||
/// </returns>
|
||||
public SerializedProperty this[int index] => _arrayProperty.GetArrayElementAtIndex(index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying serialized array property.
|
||||
/// </summary>
|
||||
public SerializedProperty arrayProperty => _arrayProperty;
|
||||
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SerializedPropertyAdaptor" />.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serialized property for entire array.</param>
|
||||
/// <param name="fixedItemHeight">Non-zero height overrides property drawer height calculation.</param>
|
||||
public SerializedPropertyAdaptor(SerializedProperty arrayProperty, float fixedItemHeight)
|
||||
{
|
||||
if (arrayProperty == null)
|
||||
{
|
||||
throw new ArgumentNullException("Array property was null.");
|
||||
}
|
||||
if (!arrayProperty.isArray)
|
||||
{
|
||||
throw new InvalidOperationException("Specified serialized propery is not an array.");
|
||||
}
|
||||
|
||||
_arrayProperty = arrayProperty;
|
||||
FixedItemHeight = fixedItemHeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SerializedPropertyAdaptor" />.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serialized property for entire array.</param>
|
||||
public SerializedPropertyAdaptor(SerializedProperty arrayProperty) : this(arrayProperty, 0f) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IReorderableListAdaptor - Implementation
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _arrayProperty.arraySize;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanDrag(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanRemove(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add()
|
||||
{
|
||||
var newIndex = _arrayProperty.arraySize;
|
||||
++_arrayProperty.arraySize;
|
||||
Internal.SerializedPropertyUtility.ResetValue(_arrayProperty.GetArrayElementAtIndex(newIndex));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index)
|
||||
{
|
||||
_arrayProperty.InsertArrayElementAtIndex(index);
|
||||
Internal.SerializedPropertyUtility.ResetValue(_arrayProperty.GetArrayElementAtIndex(index));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Duplicate(int index)
|
||||
{
|
||||
_arrayProperty.InsertArrayElementAtIndex(index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(int index)
|
||||
{
|
||||
// Unity doesn't remove element when it contains an object reference.
|
||||
var elementProperty = _arrayProperty.GetArrayElementAtIndex(index);
|
||||
if (elementProperty.propertyType == SerializedPropertyType.ObjectReference)
|
||||
{
|
||||
elementProperty.objectReferenceValue = null;
|
||||
}
|
||||
|
||||
_arrayProperty.DeleteArrayElementAtIndex(index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Move(int sourceIndex, int destIndex)
|
||||
{
|
||||
if (destIndex > sourceIndex)
|
||||
{
|
||||
--destIndex;
|
||||
}
|
||||
_arrayProperty.MoveArrayElement(sourceIndex, destIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_arrayProperty.ClearArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void BeginGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void EndGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItemBackground(Rect position, int index) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItem(Rect position, int index)
|
||||
{
|
||||
EditorGUI.PropertyField(position, this[index], GUIContent.none, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual float GetItemHeight(int index)
|
||||
{
|
||||
return FixedItemHeight != 0f
|
||||
? FixedItemHeight
|
||||
: EditorGUI.GetPropertyHeight(this[index], GUIContent.none, false)
|
||||
;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphDescription : Description, IGraphDescription { }
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(IGraph))]
|
||||
public class GraphDescriptor<TGraph, TGraphDescription> : Descriptor<TGraph, TGraphDescription>
|
||||
where TGraph : class, IGraph
|
||||
where TGraphDescription : class, IGraphDescription, new()
|
||||
{
|
||||
protected GraphDescriptor(TGraph target) : base(target) { }
|
||||
|
||||
protected TGraph graph => target;
|
||||
|
||||
[Assigns(cache = false)]
|
||||
[RequiresUnityAPI]
|
||||
public override string Title()
|
||||
{
|
||||
return StringUtility.FallbackWhitespace(graph.title, graph.GetType().HumanName());
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override string Summary()
|
||||
{
|
||||
return graph.summary;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override EditorTexture Icon()
|
||||
{
|
||||
return graph.GetType().Icon();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class GraphElementDescription : Description, IGraphElementDescription
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphItemDescriptor<TItem, TDescription> : Descriptor<TItem, TDescription>
|
||||
where TItem : class, IGraphItem
|
||||
where TDescription : class, IDescription, new()
|
||||
{
|
||||
protected GraphItemDescriptor(TItem item) : base(item) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class GraphNesterDescriptor
|
||||
{
|
||||
public static string Title(IGraphNester nester)
|
||||
{
|
||||
var graph = nester.childGraph;
|
||||
|
||||
if (!StringUtility.IsNullOrWhiteSpace(graph?.title))
|
||||
{
|
||||
return graph?.title;
|
||||
}
|
||||
|
||||
if (nester.nest.source == GraphSource.Macro && (UnityObject)nester.nest.macro != null)
|
||||
{
|
||||
var macroName = ((UnityObject)nester.nest.macro).name;
|
||||
|
||||
if (BoltCore.Configuration.humanNaming)
|
||||
{
|
||||
return macroName.Prettify();
|
||||
}
|
||||
else
|
||||
{
|
||||
return macroName;
|
||||
}
|
||||
}
|
||||
|
||||
return nester.GetType().HumanName();
|
||||
}
|
||||
|
||||
public static string Summary(IGraphNester nester)
|
||||
{
|
||||
var graph = nester.childGraph;
|
||||
|
||||
if (!StringUtility.IsNullOrWhiteSpace(graph?.summary))
|
||||
{
|
||||
return graph?.summary;
|
||||
}
|
||||
|
||||
return nester.GetType().Summary();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphDescription : IDescription { }
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphElementDescription : IDescription
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IMachineDescription : IDescription { }
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IMacroDescription : IDescription { }
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class MachineDescription : Description, IMachineDescription { }
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(IMachine))]
|
||||
public class MachineDescriptor<TMachine, TMachineDescription> : Descriptor<TMachine, TMachineDescription>
|
||||
where TMachine : UnityObject, IMachine
|
||||
where TMachineDescription : class, IMachineDescription, new()
|
||||
{
|
||||
protected MachineDescriptor(TMachine target) : base(target) { }
|
||||
|
||||
protected TMachine machine => target;
|
||||
|
||||
[Assigns(cache = false)]
|
||||
[RequiresUnityAPI]
|
||||
public override string Title()
|
||||
{
|
||||
return machine.name;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override string Summary()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override EditorTexture Icon()
|
||||
{
|
||||
return machine.GetType().Icon();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class MacroDescription : Description, IMacroDescription { }
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(IMacro))]
|
||||
public class MacroDescriptor<TMacro, TMacroDescription> : Descriptor<TMacro, TMacroDescription>
|
||||
where TMacro : UnityObject, IMacro
|
||||
where TMacroDescription : class, IMacroDescription, new()
|
||||
{
|
||||
protected MacroDescriptor(TMacro target) : base(target) { }
|
||||
|
||||
protected TMacro macro => target;
|
||||
|
||||
[Assigns(cache = false)]
|
||||
[RequiresUnityAPI]
|
||||
public override string Title()
|
||||
{
|
||||
return macro.name;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override string Summary()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override EditorTexture Icon()
|
||||
{
|
||||
return macro.GetType().Icon();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class Description : IDescription
|
||||
{
|
||||
public virtual string title { get; set; }
|
||||
public virtual string summary { get; set; }
|
||||
public virtual EditorTexture icon { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Descriptor<TTarget, TDescription> : Assigner<TTarget, TDescription>, IDescriptor
|
||||
where TTarget : class
|
||||
where TDescription : class, IDescription, new()
|
||||
{
|
||||
protected Descriptor(TTarget target) : base(target, new TDescription()) { }
|
||||
|
||||
public override void ValueChanged()
|
||||
{
|
||||
DescriptorProvider.instance.TriggerDescriptionChange(target);
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
public virtual string Title()
|
||||
{
|
||||
return target.ToString();
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
public virtual string Summary()
|
||||
{
|
||||
return target.GetType().Summary();
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public virtual EditorTexture Icon()
|
||||
{
|
||||
return target.GetType().Icon();
|
||||
}
|
||||
|
||||
object IDescriptor.target => target;
|
||||
|
||||
public TDescription description => assignee;
|
||||
|
||||
IDescription IDescriptor.description => description;
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class DescriptorAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public DescriptorAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class DescriptorProvider : SingleDecoratorProvider<object, IDescriptor, DescriptorAttribute>, IDisposable
|
||||
{
|
||||
private readonly Dictionary<object, HashSet<Action>> listeners = new Dictionary<object, HashSet<Action>>();
|
||||
|
||||
protected override bool cache => true;
|
||||
|
||||
private DescriptorProvider()
|
||||
{
|
||||
PluginContainer.delayCall += () => // The provider gets created at runtime on Start for the debug data
|
||||
{
|
||||
BoltCore.Configuration.namingSchemeChanged += DescribeAll;
|
||||
XmlDocumentation.loadComplete += DescribeAll;
|
||||
};
|
||||
}
|
||||
|
||||
public override bool IsValid(object described)
|
||||
{
|
||||
return !described.IsUnityNull();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BoltCore.Configuration.namingSchemeChanged -= DescribeAll;
|
||||
XmlDocumentation.loadComplete -= DescribeAll;
|
||||
ClearListeners();
|
||||
}
|
||||
|
||||
public void AddListener(object describable, Action onDescriptionChange)
|
||||
{
|
||||
if (!listeners.ContainsKey(describable))
|
||||
{
|
||||
listeners.Add(describable, new HashSet<Action>());
|
||||
}
|
||||
|
||||
listeners[describable].Add(onDescriptionChange);
|
||||
}
|
||||
|
||||
public void RemoveListener(object describable, Action onDescriptionChange)
|
||||
{
|
||||
if (!listeners.ContainsKey(describable))
|
||||
{
|
||||
Debug.LogWarning($"Trying to remove unknown description change listener for '{describable}'.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
listeners[describable].Remove(onDescriptionChange);
|
||||
|
||||
if (listeners[describable].Count == 0)
|
||||
{
|
||||
listeners.Remove(describable);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearListeners()
|
||||
{
|
||||
listeners.Clear();
|
||||
}
|
||||
|
||||
public void TriggerDescriptionChange(object describable)
|
||||
{
|
||||
if (!listeners.ContainsKey(describable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var onDescriptionChange in listeners[describable])
|
||||
{
|
||||
onDescriptionChange?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void Describe(object describable)
|
||||
{
|
||||
GetDecorator(describable).isDirty = true;
|
||||
}
|
||||
|
||||
public void DescribeAll()
|
||||
{
|
||||
foreach (var descriptor in decorators.Values)
|
||||
{
|
||||
descriptor.isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public IDescriptor Descriptor(object target)
|
||||
{
|
||||
return GetDecorator(target);
|
||||
}
|
||||
|
||||
public TDescriptor Descriptor<TDescriptor>(object target) where TDescriptor : IDescriptor
|
||||
{
|
||||
return GetDecorator<TDescriptor>(target);
|
||||
}
|
||||
|
||||
public IDescription Description(object target)
|
||||
{
|
||||
var descriptor = Descriptor(target);
|
||||
descriptor.Validate();
|
||||
return descriptor.description;
|
||||
}
|
||||
|
||||
public TDescription Description<TDescription>(object target) where TDescription : IDescription
|
||||
{
|
||||
var description = Description(target);
|
||||
|
||||
if (!(description is TDescription))
|
||||
{
|
||||
throw new InvalidCastException($"Description type mismatch for '{target}': found {description.GetType()}, expected {typeof(TDescription)}.");
|
||||
}
|
||||
|
||||
return (TDescription)description;
|
||||
}
|
||||
|
||||
static DescriptorProvider()
|
||||
{
|
||||
instance = new DescriptorProvider();
|
||||
}
|
||||
|
||||
public static DescriptorProvider instance { get; }
|
||||
}
|
||||
|
||||
public static class XDescriptorProvider
|
||||
{
|
||||
public static void Describe(this object target)
|
||||
{
|
||||
DescriptorProvider.instance.Describe(target);
|
||||
}
|
||||
|
||||
public static bool HasDescriptor(this object target)
|
||||
{
|
||||
Ensure.That(nameof(target)).IsNotNull(target);
|
||||
|
||||
return DescriptorProvider.instance.HasDecorator(target.GetType());
|
||||
}
|
||||
|
||||
public static IDescriptor Descriptor(this object target)
|
||||
{
|
||||
return DescriptorProvider.instance.Descriptor(target);
|
||||
}
|
||||
|
||||
public static TDescriptor Descriptor<TDescriptor>(this object target) where TDescriptor : IDescriptor
|
||||
{
|
||||
return DescriptorProvider.instance.Descriptor<TDescriptor>(target);
|
||||
}
|
||||
|
||||
public static IDescription Description(this object target)
|
||||
{
|
||||
return DescriptorProvider.instance.Description(target);
|
||||
}
|
||||
|
||||
public static TDescription Description<TDescription>(this object target) where TDescription : IDescription
|
||||
{
|
||||
return DescriptorProvider.instance.Description<TDescription>(target);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IDescription
|
||||
{
|
||||
string title { get; }
|
||||
string summary { get; }
|
||||
EditorTexture icon { get; }
|
||||
}
|
||||
|
||||
public static class XDescription
|
||||
{
|
||||
public static GUIContent ToGUIContent(this IDescription description)
|
||||
{
|
||||
return new GUIContent(description.title, null, description.summary);
|
||||
}
|
||||
|
||||
public static GUIContent ToGUIContent(this IDescription description, int iconSize)
|
||||
{
|
||||
return new GUIContent(description.title, description.icon?[iconSize], description.summary);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IDescriptor
|
||||
{
|
||||
object target { get; }
|
||||
|
||||
IDescription description { get; }
|
||||
|
||||
bool isDirty { get; set; }
|
||||
|
||||
void Validate();
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class DocumentationGenerator
|
||||
{
|
||||
public static string GenerateDocumentation(string projectPath)
|
||||
{
|
||||
PathUtility.CreateDirectoryIfNeeded(BoltCore.Paths.assemblyDocumentations);
|
||||
|
||||
var projectName = Path.GetFileNameWithoutExtension(projectPath);
|
||||
|
||||
if (!File.Exists(projectPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Project file not found: '{projectPath}'.");
|
||||
}
|
||||
|
||||
var projectBuilderPath = Paths.projectBuilder;
|
||||
|
||||
if (!File.Exists(projectBuilderPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Project builder not found: '{projectBuilderPath}'.\nYou can download the latest MSBuild from: {Paths.MsBuildDownloadLink}");
|
||||
}
|
||||
|
||||
using (var process = new Process())
|
||||
{
|
||||
var projectXml = XDocument.Load(projectPath);
|
||||
var projectRootNamespace = projectXml.Root.GetDefaultNamespace();
|
||||
var assemblyName = projectXml.Descendants(projectRootNamespace + "AssemblyName").Single().Value;
|
||||
var documentationPath = Path.Combine(BoltCore.Paths.assemblyDocumentations, assemblyName + ".xml");
|
||||
|
||||
process.StartInfo = new ProcessStartInfo();
|
||||
process.StartInfo.FileName = projectBuilderPath;
|
||||
|
||||
process.StartInfo.Arguments =
|
||||
"/p:Configuration=Debug " +
|
||||
"/p:GenerateDocumentation=true " +
|
||||
"/p:WarningLevel=0 " +
|
||||
$"/p:DocumentationFile=\"{documentationPath}\" " +
|
||||
$"\"{projectPath}\"";
|
||||
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
|
||||
var timeout = 20000;
|
||||
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
|
||||
using (var outputWaitHandle = new AutoResetEvent(false))
|
||||
using (var errorWaitHandle = new AutoResetEvent(false))
|
||||
{
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
outputWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
errorWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
if (process.WaitForExit(timeout) &&
|
||||
outputWaitHandle.WaitOne(timeout) &&
|
||||
errorWaitHandle.WaitOne(timeout))
|
||||
{
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new BuildFailedException($"Failed to build project '{projectName}':\n{process.StartInfo.Arguments}\n{error}\n{output}");
|
||||
}
|
||||
|
||||
XmlDocumentation.ClearCache();
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException("Build process timed out.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,378 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[BackgroundWorker]
|
||||
public static class XmlDocumentation
|
||||
{
|
||||
static XmlDocumentation()
|
||||
{
|
||||
documentations = new Dictionary<Assembly, Dictionary<string, XmlDocumentationTags>>();
|
||||
typeDocumentations = new Dictionary<Type, XmlDocumentationTags>();
|
||||
memberDocumentations = new Dictionary<MemberInfo, XmlDocumentationTags>();
|
||||
enumDocumentations = new Dictionary<object, XmlDocumentationTags>();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Assembly, Dictionary<string, XmlDocumentationTags>> documentations;
|
||||
private static readonly Dictionary<Type, XmlDocumentationTags> typeDocumentations;
|
||||
private static readonly Dictionary<MemberInfo, XmlDocumentationTags> memberDocumentations;
|
||||
private static readonly Dictionary<object, XmlDocumentationTags> enumDocumentations;
|
||||
private static readonly object @lock = new object();
|
||||
|
||||
public static event Action loadComplete;
|
||||
|
||||
public static bool loaded { get; private set; }
|
||||
|
||||
private static readonly string[] fallbackDirectories =
|
||||
{
|
||||
BoltCore.Paths.dotNetDocumentation,
|
||||
BoltCore.Paths.assemblyDocumentations
|
||||
};
|
||||
|
||||
public static void BackgroundWork()
|
||||
{
|
||||
var preloadedAssemblies = new List<Assembly>();
|
||||
|
||||
preloadedAssemblies.AddRange(Codebase.settingsAssemblies);
|
||||
preloadedAssemblies.AddRange(Codebase.ludiqEditorAssemblies);
|
||||
|
||||
for (var i = 0; i < preloadedAssemblies.Count; i++)
|
||||
{
|
||||
var assembly = preloadedAssemblies[i];
|
||||
ProgressUtility.DisplayProgressBar($"Documentation ({assembly.GetName().Name})...", null, (float)i / Codebase.settingsAssemblies.Count);
|
||||
var documentation = GetDocumentationUncached(assembly);
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
if (!documentations.ContainsKey(assembly))
|
||||
{
|
||||
documentations.Add(assembly, documentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnityAPI.Async(() =>
|
||||
{
|
||||
loaded = true;
|
||||
loadComplete?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
documentations.Clear();
|
||||
typeDocumentations.Clear();
|
||||
memberDocumentations.Clear();
|
||||
enumDocumentations.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, XmlDocumentationTags> Documentation(Assembly assembly)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!documentations.ContainsKey(assembly))
|
||||
{
|
||||
documentations.Add(assembly, GetDocumentationUncached(assembly));
|
||||
}
|
||||
|
||||
return documentations[assembly];
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, XmlDocumentationTags> GetDocumentationUncached(Assembly assembly)
|
||||
{
|
||||
var assemblyPath = assembly.Location;
|
||||
|
||||
var documentationPath = Path.ChangeExtension(assemblyPath, ".xml");
|
||||
|
||||
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
documentationPath = "/" + documentationPath;
|
||||
#endif
|
||||
|
||||
if (!File.Exists(documentationPath))
|
||||
{
|
||||
foreach (var fallbackDirectory in fallbackDirectories)
|
||||
{
|
||||
if (Directory.Exists(fallbackDirectory))
|
||||
{
|
||||
var fallbackPath = Path.Combine(fallbackDirectory, Path.GetFileName(documentationPath));
|
||||
|
||||
if (File.Exists(fallbackPath))
|
||||
{
|
||||
documentationPath = fallbackPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(documentationPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
XDocument document;
|
||||
|
||||
try
|
||||
{
|
||||
document = XDocument.Load(documentationPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("Failed to load XML documentation:\n" + ex);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var ns = document.Root.Name.Namespace;
|
||||
|
||||
var dictionary = new Dictionary<string, XmlDocumentationTags>();
|
||||
|
||||
foreach (var memberElement in document.Element(ns + "doc").Element(ns + "members").Elements(ns + "member"))
|
||||
{
|
||||
var nameAttribute = memberElement.Attribute("name");
|
||||
|
||||
if (nameAttribute != null)
|
||||
{
|
||||
if (dictionary.ContainsKey(nameAttribute.Value))
|
||||
{
|
||||
// Unity sometimes has duplicate member documentation in their XMLs.
|
||||
// Safely skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
dictionary.Add(nameAttribute.Value, new XmlDocumentationTags(memberElement));
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public static XmlDocumentationTags Documentation(this MemberInfo memberInfo)
|
||||
{
|
||||
if (memberInfo is Type)
|
||||
{
|
||||
return ((Type)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is MethodInfo)
|
||||
{
|
||||
return ((MethodInfo)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is FieldInfo)
|
||||
{
|
||||
return ((FieldInfo)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is PropertyInfo)
|
||||
{
|
||||
return ((PropertyInfo)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is ConstructorInfo)
|
||||
{
|
||||
return ((ConstructorInfo)memberInfo).Documentation();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ParameterSummary(this MethodBase methodBase, ParameterInfo parameterInfo)
|
||||
{
|
||||
return methodBase.Documentation()?.ParameterSummary(parameterInfo);
|
||||
}
|
||||
|
||||
public static string Summary(this MemberInfo memberInfo)
|
||||
{
|
||||
return memberInfo.Documentation()?.summary;
|
||||
}
|
||||
|
||||
public static string Summary(this Enum @enum)
|
||||
{
|
||||
return @enum.Documentation()?.summary;
|
||||
}
|
||||
|
||||
public static XmlDocumentationTags Documentation(this Enum @enum)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!enumDocumentations.ContainsKey(@enum))
|
||||
{
|
||||
enumDocumentations.Add(@enum, GetDocumentationFromNameInherited(@enum.GetType(), 'F', @enum.ToString(), null));
|
||||
}
|
||||
|
||||
return enumDocumentations[@enum];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this MethodInfo methodInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(methodInfo))
|
||||
{
|
||||
var methodDocumentation = GetDocumentationFromNameInherited(methodInfo.DeclaringType, 'M', methodInfo.Name, methodInfo.GetParameters());
|
||||
|
||||
methodDocumentation?.CompleteWithMethodBase(methodInfo, methodInfo.ReturnType);
|
||||
|
||||
memberDocumentations.Add(methodInfo, methodDocumentation);
|
||||
}
|
||||
|
||||
return memberDocumentations[methodInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this FieldInfo fieldInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(fieldInfo))
|
||||
{
|
||||
memberDocumentations.Add(fieldInfo, GetDocumentationFromNameInherited(fieldInfo.DeclaringType, 'F', fieldInfo.Name, null));
|
||||
}
|
||||
|
||||
return memberDocumentations[fieldInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this PropertyInfo propertyInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(propertyInfo))
|
||||
{
|
||||
memberDocumentations.Add(propertyInfo, GetDocumentationFromNameInherited(propertyInfo.DeclaringType, 'P', propertyInfo.Name, null));
|
||||
}
|
||||
|
||||
return memberDocumentations[propertyInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this ConstructorInfo constructorInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(constructorInfo))
|
||||
{
|
||||
var constructorDocumentation = GetDocumentationFromNameInherited(constructorInfo.DeclaringType, 'M', "#ctor", constructorInfo.GetParameters());
|
||||
|
||||
constructorDocumentation?.CompleteWithMethodBase(constructorInfo, constructorInfo.DeclaringType);
|
||||
|
||||
memberDocumentations.Add(constructorInfo, constructorDocumentation);
|
||||
}
|
||||
|
||||
return memberDocumentations[constructorInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this Type type)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!typeDocumentations.ContainsKey(type))
|
||||
{
|
||||
typeDocumentations.Add(type, GetDocumentationFromNameInherited(type, 'T', null, null));
|
||||
}
|
||||
|
||||
return typeDocumentations[type];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags GetDocumentationFromNameInherited(Type type, char prefix, string memberName, IEnumerable<ParameterInfo> parameterTypes)
|
||||
{
|
||||
var documentation = GetDocumentationFromName(type, prefix, memberName, parameterTypes);
|
||||
|
||||
if (documentation != null && documentation.inherit)
|
||||
{
|
||||
foreach (var implementedType in type.BaseTypeAndInterfaces())
|
||||
{
|
||||
var implementedDocumentation = GetDocumentationFromNameInherited(implementedType, prefix, memberName, parameterTypes);
|
||||
|
||||
if (implementedDocumentation != null)
|
||||
{
|
||||
return implementedDocumentation;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return documentation;
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags GetDocumentationFromName(Type type, char prefix, string memberName, IEnumerable<ParameterInfo> parameterTypes)
|
||||
{
|
||||
var documentation = Documentation(type.Assembly);
|
||||
|
||||
if (documentation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
type = type.GetGenericTypeDefinition();
|
||||
}
|
||||
|
||||
var fullName = $"{prefix}:{type.Namespace}{(type.Namespace != null ? "." : "")}{type.Name.Replace('+', '.')}";
|
||||
|
||||
if (!string.IsNullOrEmpty(memberName))
|
||||
{
|
||||
fullName += "." + memberName;
|
||||
|
||||
if (parameterTypes != null && parameterTypes.Any())
|
||||
{
|
||||
fullName += "(" + string.Join(",", parameterTypes.Select(p => p.ParameterType.ToString() + (p.IsOut || p.ParameterType.IsByRef ? "@" : "")).ToArray()) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
if (documentation.ContainsKey(fullName))
|
||||
{
|
||||
return documentation[fullName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class XmlDocumentationTags
|
||||
{
|
||||
public XmlDocumentationTags()
|
||||
{
|
||||
parameterTypes = new Dictionary<string, Type>();
|
||||
parameters = new Dictionary<string, string>();
|
||||
typeParameters = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public XmlDocumentationTags(string summary) : this()
|
||||
{
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public XmlDocumentationTags(XElement xml) : this()
|
||||
{
|
||||
foreach (var childNode in xml.Elements())
|
||||
{
|
||||
if (childNode.Name.LocalName == "summary")
|
||||
{
|
||||
summary = ProcessText(childNode.Value);
|
||||
}
|
||||
else if (childNode.Name.LocalName == "returns")
|
||||
{
|
||||
returns = ProcessText(childNode.Value);
|
||||
}
|
||||
else if (childNode.Name.LocalName == "remarks")
|
||||
{
|
||||
remarks = ProcessText(childNode.Value);
|
||||
}
|
||||
else if (childNode.Name.LocalName == "param")
|
||||
{
|
||||
var paramText = ProcessText(childNode.Value);
|
||||
|
||||
var nameAttribute = childNode.Attribute("name");
|
||||
|
||||
if (paramText != null && nameAttribute != null)
|
||||
{
|
||||
if (parameters.ContainsKey(nameAttribute.Value))
|
||||
{
|
||||
parameters[nameAttribute.Value] = paramText;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add(nameAttribute.Value, paramText);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childNode.Name.LocalName == "typeparam")
|
||||
{
|
||||
var typeParamText = ProcessText(childNode.Value);
|
||||
|
||||
var nameAttribute = childNode.Attribute("name");
|
||||
|
||||
if (typeParamText != null && nameAttribute != null)
|
||||
{
|
||||
if (typeParameters.ContainsKey(nameAttribute.Value))
|
||||
{
|
||||
typeParameters[nameAttribute.Value] = typeParamText;
|
||||
}
|
||||
else
|
||||
{
|
||||
typeParameters.Add(nameAttribute.Value, typeParamText);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childNode.Name.LocalName == "inheritdoc")
|
||||
{
|
||||
inherit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool methodBaseCompleted = true;
|
||||
|
||||
public string summary { get; set; }
|
||||
public string returns { get; set; }
|
||||
public string remarks { get; set; }
|
||||
public Dictionary<string, string> parameters { get; private set; }
|
||||
public Dictionary<string, string> typeParameters { get; private set; }
|
||||
public Dictionary<string, Type> parameterTypes { get; private set; }
|
||||
public Type returnType { get; set; }
|
||||
public bool inherit { get; private set; }
|
||||
|
||||
public void CompleteWithMethodBase(MethodBase methodBase, Type returnType)
|
||||
{
|
||||
if (methodBaseCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parameterInfos = methodBase.GetParameters();
|
||||
|
||||
foreach (var parameterInfo in parameterInfos)
|
||||
{
|
||||
parameterTypes.Add(parameterInfo.Name, parameterInfo.ParameterType);
|
||||
}
|
||||
|
||||
// Remove parameter summaries if no matching parameter is found.
|
||||
// (Happens frequently in Unity methods)
|
||||
foreach (var parameter in parameters.ToArray())
|
||||
{
|
||||
if (parameterInfos.All(p => p.Name != parameter.Key))
|
||||
{
|
||||
parameters.Remove(parameter.Key);
|
||||
}
|
||||
}
|
||||
|
||||
methodBaseCompleted = true;
|
||||
}
|
||||
|
||||
public string ParameterSummary(ParameterInfo parameter)
|
||||
{
|
||||
if (parameters.ContainsKey(parameter.Name))
|
||||
{
|
||||
return parameters[parameter.Name];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ProcessText(string xmlText)
|
||||
{
|
||||
xmlText = string.Join(" ", xmlText.Trim().Split(new[] { ' ', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
if (xmlText == string.Empty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return xmlText;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 989 B |
Binary file not shown.
After Width: | Height: | Size: 945 B |
Binary file not shown.
After Width: | Height: | Size: 935 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user