This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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; }
}
}

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Unity.VisualScripting
{
public abstract class Analysis : IAnalysis
{
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
public class GraphElementAnalysis : IGraphElementAnalysis
{
public List<Warning> warnings { get; set; } = new List<Warning>();
}
}

View File

@@ -0,0 +1,11 @@
namespace Unity.VisualScripting
{
public interface IAnalyser
{
IAnalysis analysis { get; }
bool isDirty { get; set; }
void Validate();
}
}

View File

@@ -0,0 +1,6 @@
namespace Unity.VisualScripting
{
public interface IAnalysis
{
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
public interface IGraphElementAnalysis : IAnalysis
{
List<Warning> warnings { get; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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[]>();
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public interface IAssigner
{
void ValueChanged();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}
}

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
public enum CanvasControlScheme
{
Unity,
Unreal
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Unity.VisualScripting
{
public interface IGraphContextExtension : IDragAndDropHandler
{
IEnumerable<GraphContextMenuItem> contextMenuItems { get; }
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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();
}
}

View File

@@ -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>();
}
}

View File

@@ -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;
}
}
}
}
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}

View File

@@ -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,
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,4 @@
namespace Unity.VisualScripting
{
public class GraphDescription : Description, IGraphDescription { }
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Unity.VisualScripting
{
public abstract class GraphElementDescription : Description, IGraphElementDescription
{
}
}

View File

@@ -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) { }
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,4 @@
namespace Unity.VisualScripting
{
public interface IGraphDescription : IDescription { }
}

View File

@@ -0,0 +1,6 @@
namespace Unity.VisualScripting
{
public interface IGraphElementDescription : IDescription
{
}
}

View File

@@ -0,0 +1,4 @@
namespace Unity.VisualScripting
{
public interface IMachineDescription : IDescription { }
}

View File

@@ -0,0 +1,4 @@
namespace Unity.VisualScripting
{
public interface IMacroDescription : IDescription { }
}

View File

@@ -0,0 +1,4 @@
namespace Unity.VisualScripting
{
public class MachineDescription : Description, IMachineDescription { }
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,4 @@
namespace Unity.VisualScripting
{
public class MacroDescription : Description, IMacroDescription { }
}

View File

@@ -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();
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,13 @@
namespace Unity.VisualScripting
{
public interface IDescriptor
{
object target { get; }
IDescription description { get; }
bool isDirty { get; set; }
void Validate();
}
}

View File

@@ -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.");
}
}
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More