testss
@@ -0,0 +1,27 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Analyser<TTarget, TAnalysis> : Assigner<TTarget, TAnalysis>, IAnalyser
|
||||
where TAnalysis : class, IAnalysis, new()
|
||||
{
|
||||
protected Analyser(GraphReference reference, TTarget target) : base(target, new TAnalysis())
|
||||
{
|
||||
Ensure.That(nameof(reference)).IsNotNull(reference);
|
||||
|
||||
this.reference = reference;
|
||||
|
||||
// HACK: It makes more sense to think of analysis as reference-bound,
|
||||
// however in practice they are context-bound and therefore it is safe
|
||||
// (and more importantly faster) to cache the context once for recursive
|
||||
// analyses.
|
||||
this.context = reference.Context();
|
||||
}
|
||||
|
||||
public TAnalysis analysis => assignee;
|
||||
|
||||
IAnalysis IAnalyser.analysis => analysis;
|
||||
|
||||
protected IGraphContext context { get; }
|
||||
|
||||
public GraphReference reference { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class AnalyserAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public AnalyserAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class AnalyserProvider : SingleDecoratorProvider<object, IAnalyser, AnalyserAttribute>
|
||||
{
|
||||
protected override bool cache => true;
|
||||
|
||||
public GraphReference reference { get; }
|
||||
|
||||
public AnalyserProvider(GraphReference reference)
|
||||
{
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
protected override IAnalyser CreateDecorator(Type decoratorType, object decorated)
|
||||
{
|
||||
return (IAnalyser)decoratorType.Instantiate(true, reference, decorated);
|
||||
}
|
||||
|
||||
public override bool IsValid(object analyzed)
|
||||
{
|
||||
return !analyzed.IsUnityNull();
|
||||
}
|
||||
|
||||
public void Analyze(object analyzed)
|
||||
{
|
||||
GetDecorator(analyzed).isDirty = true;
|
||||
}
|
||||
|
||||
public void AnalyzeAll()
|
||||
{
|
||||
foreach (var analyser in decorators.Values)
|
||||
{
|
||||
analyser.isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class XAnalyserProvider
|
||||
{
|
||||
// Analysis are conceptually reference-bound, but practically context-bound,
|
||||
// so it's faster to avoid the reference-to-context lookup if we can avoid it.
|
||||
|
||||
public static IAnalyser Analyser(this object target, IGraphContext context)
|
||||
{
|
||||
return context.analyserProvider.GetDecorator(target);
|
||||
}
|
||||
|
||||
public static TAnalyser Analyser<TAnalyser>(this object target, IGraphContext context) where TAnalyser : IAnalyser
|
||||
{
|
||||
return context.analyserProvider.GetDecorator<TAnalyser>(target);
|
||||
}
|
||||
|
||||
public static IAnalysis Analysis(this object target, IGraphContext context)
|
||||
{
|
||||
var analyser = target.Analyser(context);
|
||||
analyser.Validate();
|
||||
return analyser.analysis;
|
||||
}
|
||||
|
||||
public static TAnalysis Analysis<TAnalysis>(this object target, IGraphContext context) where TAnalysis : IAnalysis
|
||||
{
|
||||
return (TAnalysis)target.Analysis(context);
|
||||
}
|
||||
|
||||
// Shortcuts, but the above are faster because Context doesn't have to be looked up
|
||||
|
||||
public static IAnalyser Analyser(this object target, GraphReference reference)
|
||||
{
|
||||
return target.Analyser(reference.Context());
|
||||
}
|
||||
|
||||
public static TAnalyser Analyser<TAnalyser>(this object target, GraphReference reference) where TAnalyser : IAnalyser
|
||||
{
|
||||
return target.Analyser<TAnalyser>(reference.Context());
|
||||
}
|
||||
|
||||
public static IAnalysis Analysis(this object target, GraphReference reference)
|
||||
{
|
||||
return target.Analysis(reference.Context());
|
||||
}
|
||||
|
||||
public static TAnalysis Analysis<TAnalysis>(this object target, GraphReference reference) where TAnalysis : IAnalysis
|
||||
{
|
||||
return target.Analysis<TAnalysis>(reference.Context());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Analysis : IAnalysis
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphElementAnalysis : IGraphElementAnalysis
|
||||
{
|
||||
public List<Warning> warnings { get; set; } = new List<Warning>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IAnalyser
|
||||
{
|
||||
IAnalysis analysis { get; }
|
||||
|
||||
bool isDirty { get; set; }
|
||||
|
||||
void Validate();
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IAnalysis
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphElementAnalysis : IAnalysis
|
||||
{
|
||||
List<Warning> warnings { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class UsageAnalytics
|
||||
{
|
||||
const int k_MaxEventsPerHour = 1000;
|
||||
const int k_MaxNumberOfElements = 1000;
|
||||
const string k_VendorKey = "unity.bolt";
|
||||
const string k_EventName = "BoltUsage";
|
||||
static bool isRegistered = false;
|
||||
|
||||
public static void CollectAndSend()
|
||||
{
|
||||
if (!EditorAnalytics.enabled)
|
||||
return;
|
||||
|
||||
if (!RegisterEvent())
|
||||
return;
|
||||
|
||||
var data = CollectData();
|
||||
|
||||
EditorAnalytics.SendEventWithLimit(k_EventName, data);
|
||||
}
|
||||
|
||||
private static bool RegisterEvent()
|
||||
{
|
||||
if (!isRegistered)
|
||||
{
|
||||
var result = EditorAnalytics.RegisterEventWithLimit(k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey);
|
||||
if (result == UnityEngine.Analytics.AnalyticsResult.Ok)
|
||||
{
|
||||
isRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isRegistered;
|
||||
}
|
||||
|
||||
private static UsageAnalyticsData CollectData()
|
||||
{
|
||||
var data = new UsageAnalyticsData
|
||||
{
|
||||
productVersion = BoltProduct.instance.version.ToString(),
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private struct UsageAnalyticsData
|
||||
{
|
||||
public string productVersion;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Assigner<TTarget, TAssignee> : IAssigner
|
||||
where TAssignee : class
|
||||
{
|
||||
protected Assigner(TTarget target, TAssignee assignee)
|
||||
{
|
||||
Ensure.That(nameof(target)).IsNotNull(target);
|
||||
Ensure.That(nameof(assignee)).IsNotNull(assignee);
|
||||
|
||||
this.target = target;
|
||||
this.assignee = assignee;
|
||||
|
||||
var assignerType = GetType();
|
||||
|
||||
if (!_assignments.ContainsKey(assignerType))
|
||||
{
|
||||
_assignments.Add(assignerType, Assignment.Fetch(assignerType, typeof(TAssignee)).ToArray());
|
||||
_transientAssignments.Add(assignerType, _assignments[assignerType].Where(a => !a.cache).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public TTarget target { get; }
|
||||
|
||||
public TAssignee assignee { get; }
|
||||
|
||||
public bool isDirty { get; set; } = true;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (isDirty)
|
||||
{
|
||||
AssignAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
AssignTransient();
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssignAll()
|
||||
{
|
||||
isDirty = false;
|
||||
|
||||
foreach (var assignment in assignments)
|
||||
{
|
||||
assignment.Run(this, assignee);
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssignTransient()
|
||||
{
|
||||
foreach (var assignment in transientAssignments)
|
||||
{
|
||||
assignment.Run(this, assignee);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ValueChanged() { }
|
||||
|
||||
public Assignment[] assignments => _assignments[GetType()];
|
||||
public Assignment[] transientAssignments => _transientAssignments[GetType()];
|
||||
|
||||
private static readonly Dictionary<Type, Assignment[]> _assignments = new Dictionary<Type, Assignment[]>();
|
||||
|
||||
private static readonly Dictionary<Type, Assignment[]> _transientAssignments = new Dictionary<Type, Assignment[]>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class Assignment
|
||||
{
|
||||
public Assignment(Member assigner, Type assigneeType)
|
||||
{
|
||||
Ensure.That(nameof(assigneeType)).IsNotNull(assigneeType);
|
||||
|
||||
this.assigner = assigner;
|
||||
|
||||
var assignsAttribute = assigner.info.GetAttribute<AssignsAttribute>();
|
||||
assignee = new Member(assigneeType, assignsAttribute?.memberName ?? assigner.name.FirstCharacterToLower());
|
||||
requiresAPI = assigner.info.HasAttribute<RequiresUnityAPIAttribute>();
|
||||
cache = assignsAttribute?.cache ?? true;
|
||||
|
||||
assigner.Prewarm();
|
||||
assignee.Prewarm();
|
||||
}
|
||||
|
||||
public Member assigner { get; }
|
||||
public Member assignee { get; }
|
||||
public bool requiresAPI { get; }
|
||||
public bool cache { get; }
|
||||
|
||||
public void Run(object assigner, object assignee)
|
||||
{
|
||||
if (requiresAPI)
|
||||
{
|
||||
UnityAPI.Async(() => _Run(assigner, assignee));
|
||||
}
|
||||
else
|
||||
{
|
||||
_Run(assigner, assignee);
|
||||
}
|
||||
}
|
||||
|
||||
private void _Run(object assigner, object assignee)
|
||||
{
|
||||
var oldValue = this.assignee.Get(assignee);
|
||||
var newValue = ConversionUtility.Convert(this.assigner.Invoke(assigner), this.assignee.type);
|
||||
|
||||
this.assignee.Set(assignee, newValue);
|
||||
|
||||
if (!Equals(oldValue, newValue))
|
||||
{
|
||||
if (assigner is IAssigner _assigner)
|
||||
{
|
||||
_assigner.ValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Assignment> Fetch(Type descriptorType, Type descriptionType)
|
||||
{
|
||||
var bindingFlags = BindingFlags.Instance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic;
|
||||
|
||||
return descriptorType.GetMethods(bindingFlags)
|
||||
.Where(m => m.HasAttribute<AssignsAttribute>())
|
||||
.Select(m => new Assignment(m.ToManipulator(descriptorType), descriptionType));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class AssignsAttribute : Attribute
|
||||
{
|
||||
public AssignsAttribute() { }
|
||||
|
||||
public AssignsAttribute(string memberName)
|
||||
{
|
||||
this.memberName = memberName;
|
||||
}
|
||||
|
||||
public string memberName { get; }
|
||||
|
||||
public bool cache { get; set; } = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IAssigner
|
||||
{
|
||||
void ValueChanged();
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEvent = UnityEngine.Event;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class BoltGUI
|
||||
{
|
||||
private static UnityEvent e => UnityEvent.current;
|
||||
|
||||
public static float GetVariableFieldHeight(GUIContent label, string value, ActionDirection direction)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
|
||||
public static string VariableField(Rect position, GUIContent label, string value, ActionDirection direction)
|
||||
{
|
||||
position = EditorGUI.PrefixLabel(position, label);
|
||||
var style = direction != ActionDirection.Any ? BoltStyles.variableFieldWithDirectionIndicator : BoltStyles.variableFieldWithoutDirectionIndicator;
|
||||
value = EditorGUI.TextField(position, GUIContent.none, value, style);
|
||||
|
||||
var iconPosition = new Rect
|
||||
(
|
||||
position.x + 3,
|
||||
position.y + 2,
|
||||
12,
|
||||
12
|
||||
);
|
||||
|
||||
GUI.DrawTexture(iconPosition, BoltCore.Icons.variable?[IconSize.Small]);
|
||||
|
||||
if (direction != ActionDirection.Any)
|
||||
{
|
||||
var arrowPosition = new Rect
|
||||
(
|
||||
iconPosition.xMax + (direction == ActionDirection.Get ? 12 : -2),
|
||||
position.y + 2,
|
||||
12 * (direction == ActionDirection.Get ? -1 : 1),
|
||||
12
|
||||
);
|
||||
|
||||
if (e.type == EventType.Repaint)
|
||||
{
|
||||
BoltStyles.variableFieldDirectionIndicator.Draw(arrowPosition, false, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Product(ID)]
|
||||
public sealed class BoltProduct : Product
|
||||
{
|
||||
public BoltProduct() { }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
logo = BoltCore.Resources.LoadTexture("Logos/LogoBolt.png", CreateTextureOptions.Scalable)?.Single();
|
||||
}
|
||||
|
||||
public override string configurationPanelLabel => "Bolt";
|
||||
|
||||
public override string name => "Bolt";
|
||||
public override string description => "";
|
||||
public override string authorLabel => "Designed & Developed by ";
|
||||
public override string author => "";
|
||||
public override string copyrightHolder => "Unity";
|
||||
public override SemanticVersion version => "1.5.1";
|
||||
public override string publisherUrl => "";
|
||||
public override string websiteUrl => "";
|
||||
public override string supportUrl => "";
|
||||
public override string manualUrl => "https://docs.unity3d.com/Packages/com.unity.bolt@latest";
|
||||
public override string assetStoreUrl => "http://u3d.as/1Md2";
|
||||
|
||||
public const string ID = "Bolt";
|
||||
|
||||
#if VISUAL_SCRIPT_INTERNAL
|
||||
public const int ToolsMenuPriority = -990000;
|
||||
public const int DeveloperToolsMenuPriority = ToolsMenuPriority + 1000;
|
||||
#endif
|
||||
|
||||
public static BoltProduct instance => (BoltProduct)ProductContainer.GetProduct(ID);
|
||||
|
||||
[SettingsProvider]
|
||||
private static SettingsProvider BoltSettingsProvider()
|
||||
{
|
||||
return new VSEditorSettingsProviderView();
|
||||
}
|
||||
|
||||
private static bool PrepareForRelease()
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog("Delete Generated Files", "This action will delete all generated files, including those containing user data.\n\nAre you sure you want to continue?", "Confirm", "Cancel"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginConfiguration.DeleteAllProjectSettings();
|
||||
|
||||
foreach (var plugin in PluginContainer.plugins)
|
||||
{
|
||||
PathUtility.DeleteDirectoryIfExists(plugin.paths.persistentGenerated);
|
||||
PathUtility.DeleteDirectoryIfExists(plugin.paths.transientGenerated);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if VISUAL_SCRIPT_INTERNAL
|
||||
[MenuItem("Tools/Bolt/Internal/Export Release Asset Package...", priority = DeveloperToolsMenuPriority + 102)]
|
||||
#endif
|
||||
private static void ExportReleasePackage()
|
||||
{
|
||||
if (!PrepareForRelease())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var exportPath = EditorUtility.SaveFilePanel("Export Release Package",
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
"Bolt_" + instance.version.ToString().Replace(".", "_").Replace(" ", "_"),
|
||||
"unitypackage");
|
||||
|
||||
if (exportPath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var packageDirectory = Path.GetDirectoryName(exportPath);
|
||||
|
||||
var paths = new List<string>()
|
||||
{
|
||||
PathUtility.GetPackageRootPath()
|
||||
};
|
||||
|
||||
AssetDatabase.ExportPackage(paths.ToArray(), exportPath, ExportPackageOptions.Recurse);
|
||||
|
||||
if (EditorUtility.DisplayDialog("Export Release Package", "Release package export complete.\nOpen containing folder?", "Open Folder", "Close"))
|
||||
{
|
||||
Process.Start(packageDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class BoltStyles
|
||||
{
|
||||
static BoltStyles()
|
||||
{
|
||||
// Variables
|
||||
|
||||
variableFieldDirectionIndicator = "AC LeftArrow";
|
||||
variableFieldWithoutDirectionIndicator = new GUIStyle(EditorStyles.textField);
|
||||
variableFieldWithoutDirectionIndicator.padding.left = IconSize.Small;
|
||||
variableFieldWithDirectionIndicator = new GUIStyle(variableFieldWithoutDirectionIndicator);
|
||||
variableFieldWithDirectionIndicator.padding.left = 24;
|
||||
}
|
||||
|
||||
// Variables
|
||||
public static readonly GUIStyle variableFieldDirectionIndicator;
|
||||
public static readonly GUIStyle variableFieldWithDirectionIndicator;
|
||||
public static readonly GUIStyle variableFieldWithoutDirectionIndicator;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public enum AlignOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Align the left edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignLeftEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Align the horizontal centers of the selected elements.
|
||||
/// </summary>
|
||||
AlignCenters,
|
||||
|
||||
/// <summary>
|
||||
/// Align the right edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignRightEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Align the top edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignTopEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Align the vertical middles of the selected elements.
|
||||
/// </summary>
|
||||
AlignMiddles,
|
||||
|
||||
/// <summary>
|
||||
/// Align the bottom edges of the selected elements.
|
||||
/// </summary>
|
||||
AlignBottomEdges
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class CanvasAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public CanvasAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public enum CanvasControlScheme
|
||||
{
|
||||
Unity,
|
||||
Unreal
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class CanvasProvider : SingleDecoratorProvider<IGraph, ICanvas, CanvasAttribute>
|
||||
{
|
||||
static CanvasProvider()
|
||||
{
|
||||
instance = new CanvasProvider();
|
||||
}
|
||||
|
||||
public static CanvasProvider instance { get; }
|
||||
|
||||
protected override bool cache => true;
|
||||
|
||||
public override bool IsValid(IGraph graph)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class XCanvasProvider
|
||||
{
|
||||
public static ICanvas Canvas(this IGraph graph)
|
||||
{
|
||||
return CanvasProvider.instance.GetDecorator(graph);
|
||||
}
|
||||
|
||||
public static TCanvas Canvas<TCanvas>(this IGraph graph) where TCanvas : ICanvas
|
||||
{
|
||||
return CanvasProvider.instance.GetDecorator<TCanvas>(graph);
|
||||
}
|
||||
|
||||
public static IWidget Widget(this ICanvas context, IGraphItem item)
|
||||
{
|
||||
return context.widgetProvider.GetDecorator(item);
|
||||
}
|
||||
|
||||
public static TWidget Widget<TWidget>(this ICanvas context, IGraphItem item) where TWidget : IWidget
|
||||
{
|
||||
return context.widgetProvider.GetDecorator<TWidget>(item);
|
||||
}
|
||||
|
||||
public static IGraphElementWidget Widget(this ICanvas context, IGraphElement element)
|
||||
{
|
||||
return (IGraphElementWidget)context.widgetProvider.GetDecorator(element);
|
||||
}
|
||||
|
||||
public static TWidget Widget<TWidget>(this ICanvas context, IGraphElement element) where TWidget : IGraphElementWidget
|
||||
{
|
||||
return context.widgetProvider.GetDecorator<TWidget>(element);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public enum DistributeOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the left edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeLeftEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the horizontal centers
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeCenters,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the right edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeRightEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the horizontal gaps
|
||||
/// are all of equal size.
|
||||
/// </summary>
|
||||
DistributeHorizontalGaps,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the top edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeTopEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the vertical middles
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeMiddles,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the bottom edges
|
||||
/// are at equal distance of one another.
|
||||
/// </summary>
|
||||
DistributeBottomEdges,
|
||||
|
||||
/// <summary>
|
||||
/// Distribute the selected elements so that the vertical gaps
|
||||
/// are all of equal size.
|
||||
/// </summary>
|
||||
DistributeVerticalGaps
|
||||
}
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface ICanvas : IDisposable, IDragAndDropHandler
|
||||
{
|
||||
void Cache();
|
||||
|
||||
#region Model
|
||||
|
||||
ICanvasWindow window { get; set; }
|
||||
|
||||
IGraph graph { get; }
|
||||
|
||||
WidgetProvider widgetProvider { get; }
|
||||
|
||||
GraphSelection selection { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Widgets
|
||||
|
||||
IEnumerable<IWidget> widgets { get; }
|
||||
|
||||
void Recollect();
|
||||
|
||||
void CacheWidgetCollections();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Hovering
|
||||
|
||||
IWidget hoveredWidget { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Deleting
|
||||
|
||||
void DeleteSelection();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Clipboard
|
||||
|
||||
void ShrinkCopyGroup(HashSet<IGraphElement> copyGroup);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
void RegisterControls();
|
||||
|
||||
void Open();
|
||||
|
||||
void Close();
|
||||
|
||||
void Update();
|
||||
|
||||
void BeforeFrame();
|
||||
|
||||
void OnGUI();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Viewing
|
||||
|
||||
float zoom { get; }
|
||||
|
||||
Vector2 pan { get; }
|
||||
|
||||
void UpdateViewport();
|
||||
|
||||
Rect viewport { get; set; }
|
||||
|
||||
Vector2 mousePosition { get; }
|
||||
|
||||
bool isMouseOver { get; }
|
||||
|
||||
bool isMouseOverBackground { get; }
|
||||
|
||||
void ViewElements(IEnumerable<IGraphElement> elements);
|
||||
|
||||
bool IsVisible(IWidget widget);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Positioning
|
||||
|
||||
void CacheWidgetPositions();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Lassoing
|
||||
|
||||
bool isLassoing { get; }
|
||||
|
||||
Rect lassoArea { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Selecting
|
||||
|
||||
bool isSelecting { get; }
|
||||
|
||||
Rect selectionArea { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Grouping
|
||||
|
||||
bool isGrouping { get; }
|
||||
|
||||
Rect groupArea { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Dragging
|
||||
|
||||
bool isDragging { get; }
|
||||
|
||||
void BeginDrag(EventWrapper e);
|
||||
|
||||
void Drag(EventWrapper e);
|
||||
|
||||
void EndDrag(EventWrapper e);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Layout
|
||||
|
||||
void Align(AlignOperation operation);
|
||||
|
||||
void Distribute(DistributeOperation operation);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Timing
|
||||
|
||||
float frameDeltaTime { get; }
|
||||
|
||||
float eventDeltaTime { get; }
|
||||
|
||||
float repaintDeltaTime { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Window
|
||||
|
||||
void OnToolbarGUI();
|
||||
|
||||
event Action delayCall;
|
||||
|
||||
Queue<Action> delayedCalls { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphContextExtension : IDragAndDropHandler
|
||||
{
|
||||
IEnumerable<GraphContextMenuItem> contextMenuItems { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of widgets that can be safely iterated over even if the collection changes during iteration.
|
||||
/// </summary>
|
||||
public class WidgetList<TWidget> : Collection<TWidget>, IEnumerable<TWidget> where TWidget : class, IWidget
|
||||
{
|
||||
private uint version;
|
||||
|
||||
private readonly ICanvas canvas;
|
||||
|
||||
public WidgetList(ICanvas canvas)
|
||||
{
|
||||
Ensure.That(nameof(canvas)).IsNotNull(canvas);
|
||||
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, TWidget item)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
version++;
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
base.RemoveItem(index);
|
||||
version++;
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
base.ClearItems();
|
||||
version++;
|
||||
}
|
||||
|
||||
public new Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
IEnumerator<TWidget> IEnumerable<TWidget>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<TWidget>
|
||||
{
|
||||
private readonly WidgetList<TWidget> list;
|
||||
|
||||
private int index;
|
||||
|
||||
private TWidget current;
|
||||
|
||||
private bool invalid;
|
||||
|
||||
private readonly uint version;
|
||||
|
||||
private IGraph graph;
|
||||
|
||||
public Enumerator(WidgetList<TWidget> list) : this()
|
||||
{
|
||||
this.list = list;
|
||||
version = list.version;
|
||||
|
||||
// Micro optim
|
||||
graph = list.canvas.graph;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
// Stop if the list changed
|
||||
if (version != list.version)
|
||||
{
|
||||
current = null;
|
||||
invalid = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Micro optim
|
||||
var count = list.Count;
|
||||
|
||||
// Find the next element that is within the graph
|
||||
while (index < count)
|
||||
{
|
||||
current = list[index];
|
||||
index++;
|
||||
|
||||
if (current.item.graph == graph)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Expleted the list
|
||||
current = null;
|
||||
invalid = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public TWidget Current => current;
|
||||
|
||||
object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (invalid)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[InitializeAfterPlugins]
|
||||
public static class GraphClipboard
|
||||
{
|
||||
static GraphClipboard()
|
||||
{
|
||||
singleClipboard = new Clipboard();
|
||||
groupClipboard = new Clipboard();
|
||||
|
||||
GraphWindow.activeContextChanged += OnContextChange;
|
||||
}
|
||||
|
||||
private static Event e => Event.current;
|
||||
|
||||
private static void OnContextChange(IGraphContext context)
|
||||
{
|
||||
GraphClipboard.context = context;
|
||||
}
|
||||
|
||||
private static IGraphContext context;
|
||||
|
||||
#region Context Shortcuts
|
||||
|
||||
private static GraphReference reference => context.reference;
|
||||
|
||||
private static IGraph graph => context.graph;
|
||||
|
||||
private static ICanvas canvas => context.canvas;
|
||||
|
||||
private static GraphSelection selection => context.selection;
|
||||
|
||||
#endregion
|
||||
|
||||
public static Clipboard singleClipboard { get; }
|
||||
|
||||
public static Clipboard groupClipboard { get; }
|
||||
|
||||
public static bool canCopySelection => selection.Count > 0;
|
||||
|
||||
public static bool canPaste
|
||||
{
|
||||
get
|
||||
{
|
||||
if (selection.Count == 1 && CanPasteInside(selection.Single()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return canPasteOutside;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool canPasteOutside => groupClipboard.containsData && GetPasteGroup().Any();
|
||||
|
||||
public static bool canDuplicateSelection => GetCopyGroup(selection).Count > 0;
|
||||
|
||||
private static HashSet<IGraphElement> GetCopyGroup(IEnumerable<IGraphElement> elements)
|
||||
{
|
||||
var copyGroup = new HashSet<IGraphElement>();
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
copyGroup.Add(element);
|
||||
|
||||
canvas.Widget(element).ExpandCopyGroup(copyGroup);
|
||||
}
|
||||
|
||||
canvas.ShrinkCopyGroup(copyGroup);
|
||||
|
||||
return copyGroup;
|
||||
}
|
||||
|
||||
private static List<IGraphElement> GetPasteGroup()
|
||||
{
|
||||
return groupClipboard.Paste<HashSet<IGraphElement>>()
|
||||
.Where(e => graph.elements.Includes(e.GetType()))
|
||||
.OrderBy(e => e.dependencyOrder)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public static void CopyElement(IGraphElement element)
|
||||
{
|
||||
Ensure.That(nameof(element)).IsNotNull(element);
|
||||
|
||||
singleClipboard.Copy(element);
|
||||
groupClipboard.Copy(GetCopyGroup(element.Yield()));
|
||||
}
|
||||
|
||||
public static void CopySelection()
|
||||
{
|
||||
if (!canCopySelection)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (selection.Count == 1)
|
||||
{
|
||||
CopyElement(selection.Single());
|
||||
}
|
||||
else
|
||||
{
|
||||
singleClipboard.Clear();
|
||||
groupClipboard.Copy(GetCopyGroup(selection));
|
||||
}
|
||||
|
||||
e?.TryUse();
|
||||
}
|
||||
|
||||
public static void Paste(Vector2? position = null)
|
||||
{
|
||||
if (!canPaste)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (selection.Count == 1 && CanPasteInside(selection.Single()))
|
||||
{
|
||||
PasteInside(selection.Single());
|
||||
}
|
||||
else
|
||||
{
|
||||
PasteOutside(true, position);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CanPasteInside(IGraphElement element)
|
||||
{
|
||||
Ensure.That(nameof(element)).IsNotNull(element);
|
||||
|
||||
// TODO: A solid PasteInside implementation would work like ReplaceUnit:
|
||||
// Implement an IPreservable interface, preserve, remove, recreate, apply.
|
||||
// This would make entirely sure that all OnAdd/OnRemove handlers get called,
|
||||
// and wouldn't require any per-element implementation. Plus, it would
|
||||
// allow pasting across element types while preserving connections/transitions!
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void PasteInside(IGraphElement element)
|
||||
{
|
||||
Ensure.That(nameof(element)).IsNotNull(element);
|
||||
|
||||
if (!CanPasteInside(element))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
UndoUtility.RecordEditedObject("Paste Graph Element");
|
||||
|
||||
throw new NotImplementedException();
|
||||
|
||||
//GUI.changed = true;
|
||||
//e?.TryUse();
|
||||
}
|
||||
|
||||
public static void PasteOutside(bool reposition, Vector2? position = null)
|
||||
{
|
||||
if (!canPasteOutside)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
UndoUtility.RecordEditedObject("Paste Graph Elements");
|
||||
|
||||
var pastedElements = GetPasteGroup();
|
||||
|
||||
// Assign new GUIDs
|
||||
|
||||
foreach (var pastedElement in pastedElements)
|
||||
{
|
||||
pastedElement.guid = Guid.NewGuid();
|
||||
}
|
||||
|
||||
// Add elements to graph and selection
|
||||
|
||||
selection.Clear();
|
||||
|
||||
foreach (var pastedElement in pastedElements)
|
||||
{
|
||||
if (!pastedElement.HandleDependencies())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
graph.elements.Add(pastedElement);
|
||||
|
||||
selection.Add(pastedElement);
|
||||
}
|
||||
|
||||
canvas.Cache();
|
||||
|
||||
foreach (var pastedElement in pastedElements)
|
||||
{
|
||||
var pastedWidget = canvas.Widget(pastedElement);
|
||||
pastedWidget.BringToFront();
|
||||
}
|
||||
|
||||
var pastedWidgets = pastedElements.Select(e => canvas.Widget(e)).ToList();
|
||||
|
||||
// Recenter elements in graph view
|
||||
|
||||
if (reposition)
|
||||
{
|
||||
var area = GraphGUI.CalculateArea(pastedWidgets.Where(widget => widget.canDrag));
|
||||
|
||||
Vector2 delta;
|
||||
|
||||
if (position.HasValue)
|
||||
{
|
||||
delta = position.Value - area.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta = graph.pan - area.center;
|
||||
}
|
||||
|
||||
foreach (var pastedWidget in pastedWidgets)
|
||||
{
|
||||
if (pastedWidget.canDrag)
|
||||
{
|
||||
pastedWidget.position = new Rect(pastedWidget.position.position + delta, pastedWidget.position.size).PixelPerfect();
|
||||
pastedWidget.Reposition();
|
||||
pastedWidget.CachePositionFirstPass();
|
||||
pastedWidget.CachePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Space out overlapping elements
|
||||
|
||||
foreach (var pastedWidget in pastedWidgets)
|
||||
{
|
||||
if (pastedWidget.canDrag)
|
||||
{
|
||||
var distanciation = 20;
|
||||
var timeout = 100;
|
||||
var timeoutIndex = 0;
|
||||
|
||||
while (GraphGUI.PositionOverlaps(canvas, pastedWidget, 5))
|
||||
{
|
||||
// Space the widget out
|
||||
pastedWidget.position = new Rect(pastedWidget.position.position + new Vector2(distanciation, distanciation), pastedWidget.position.size).PixelPerfect();
|
||||
|
||||
// Calculate the resulting position immediately
|
||||
pastedWidget.CachePositionFirstPass();
|
||||
pastedWidget.CachePosition();
|
||||
|
||||
// Mark it as invalid still so dependencies like ports will be recached
|
||||
pastedWidget.Reposition();
|
||||
|
||||
// Failsafe to keep the editor from freezing
|
||||
if (++timeoutIndex > timeout)
|
||||
{
|
||||
Debug.LogWarning($"Failed to space out pasted element: {pastedWidget.element}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Cache();
|
||||
|
||||
GUI.changed = true;
|
||||
|
||||
e?.TryUse();
|
||||
}
|
||||
|
||||
public static void CutSelection()
|
||||
{
|
||||
UndoUtility.RecordEditedObject("Cut Graph Element Selection");
|
||||
CopySelection();
|
||||
canvas.DeleteSelection();
|
||||
}
|
||||
|
||||
public static void DuplicateSelection()
|
||||
{
|
||||
object singleClipboardRestore = null;
|
||||
object groupClipboardRestore = null;
|
||||
|
||||
if (singleClipboard.containsData)
|
||||
{
|
||||
singleClipboardRestore = singleClipboard.Paste();
|
||||
}
|
||||
|
||||
if (groupClipboard.containsData)
|
||||
{
|
||||
groupClipboardRestore = groupClipboard.Paste();
|
||||
}
|
||||
|
||||
UndoUtility.RecordEditedObject("Duplicate Graph Element Selection");
|
||||
CopySelection();
|
||||
PasteOutside(false);
|
||||
|
||||
if (singleClipboardRestore != null)
|
||||
{
|
||||
singleClipboard.Copy(singleClipboardRestore);
|
||||
}
|
||||
else
|
||||
{
|
||||
singleClipboard.Clear();
|
||||
}
|
||||
|
||||
if (groupClipboardRestore != null)
|
||||
{
|
||||
groupClipboard.Copy(groupClipboardRestore);
|
||||
}
|
||||
else
|
||||
{
|
||||
groupClipboard.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class GraphContext<TGraph, TCanvas> : IGraphContext
|
||||
where TGraph : class, IGraph
|
||||
where TCanvas : class, ICanvas
|
||||
{
|
||||
protected GraphContext(GraphReference reference)
|
||||
{
|
||||
Ensure.That(nameof(reference)).IsNotNull(reference);
|
||||
Ensure.That(nameof(reference.graph)).IsOfType<TGraph>(reference.graph);
|
||||
|
||||
this.reference = reference;
|
||||
|
||||
analyserProvider = new AnalyserProvider(reference);
|
||||
graphMetadata = Metadata.Root().StaticObject(reference.graph);
|
||||
extensions = this.Extensions().ToList().AsReadOnly();
|
||||
sidebarPanels = SidebarPanels().ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public GraphReference reference { get; }
|
||||
|
||||
public AnalyserProvider analyserProvider { get; }
|
||||
|
||||
public TCanvas canvas => (TCanvas)graph.Canvas();
|
||||
|
||||
ICanvas IGraphContext.canvas => canvas;
|
||||
|
||||
public GraphSelection selection => canvas.selection;
|
||||
|
||||
public TGraph graph => (TGraph)reference.graph;
|
||||
|
||||
IGraph IGraphContext.graph => graph;
|
||||
|
||||
public Metadata graphMetadata { get; }
|
||||
|
||||
public Metadata selectionMetadata => selection.Count == 1 ? ElementMetadata(selection.Single()) : null;
|
||||
|
||||
public Metadata ElementMetadata(IGraphElement element)
|
||||
{
|
||||
// Static object is faster than indexer
|
||||
// return graphMetadata[nameof(IGraph.elements)].Indexer(element.guid).Cast(element.GetType());
|
||||
return graphMetadata[nameof(IGraph.elements)].StaticObject(element);
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<IGraphContextExtension> extensions { get; }
|
||||
|
||||
IEnumerable<IGraphContextExtension> IGraphContext.extensions => extensions;
|
||||
|
||||
public virtual string windowTitle => "Graph";
|
||||
|
||||
protected virtual IEnumerable<ISidebarPanelContent> SidebarPanels() => Enumerable.Empty<ISidebarPanelContent>();
|
||||
|
||||
public ReadOnlyCollection<ISidebarPanelContent> sidebarPanels { get; }
|
||||
|
||||
IEnumerable<ISidebarPanelContent> IGraphContext.sidebarPanels => sidebarPanels;
|
||||
|
||||
public bool isPrefabInstance => reference.serializedObject?.IsConnectedPrefabInstance() ?? false;
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Manually free the providers so that the
|
||||
// disposable decorators free their event handlers
|
||||
|
||||
analyserProvider?.FreeAll();
|
||||
}
|
||||
|
||||
public void BeginEdit(bool disablePrefabInstance = true)
|
||||
{
|
||||
LudiqEditorUtility.editedObject.BeginOverride(reference.serializedObject);
|
||||
LudiqGraphsEditorUtility.editedContext.BeginOverride(this);
|
||||
EditorGUI.BeginDisabledGroup(disablePrefabInstance && isPrefabInstance);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
}
|
||||
|
||||
public void EndEdit()
|
||||
{
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
DescribeAndAnalyze();
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
LudiqGraphsEditorUtility.editedContext.EndOverride();
|
||||
LudiqEditorUtility.editedObject.EndOverride();
|
||||
}
|
||||
|
||||
public void DescribeAndAnalyze()
|
||||
{
|
||||
foreach (var element in graph.elements)
|
||||
{
|
||||
if (element.HasDescriptor())
|
||||
{
|
||||
element.Describe();
|
||||
}
|
||||
}
|
||||
|
||||
graph.Describe();
|
||||
|
||||
analyserProvider.AnalyzeAll();
|
||||
|
||||
reference.parent.Describe();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
public sealed class GraphContextAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public GraphContextAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class GraphContextExtension<TGraphContext> : IGraphContextExtension
|
||||
where TGraphContext : IGraphContext
|
||||
{
|
||||
protected GraphContextExtension(TGraphContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public TGraphContext context { get; }
|
||||
|
||||
#region Context Shortcuts
|
||||
|
||||
protected GraphReference reference => context.reference;
|
||||
|
||||
protected IGraph graph => context.graph;
|
||||
|
||||
protected ICanvas canvas => context.canvas;
|
||||
|
||||
protected GraphSelection selection => context.selection;
|
||||
|
||||
#endregion
|
||||
|
||||
public virtual IEnumerable<GraphContextMenuItem> contextMenuItems => Enumerable.Empty<GraphContextMenuItem>();
|
||||
|
||||
public virtual DragAndDropVisualMode dragAndDropVisualMode => DragAndDropVisualMode.Generic;
|
||||
|
||||
public virtual bool AcceptsDragAndDrop() => false;
|
||||
|
||||
public virtual void PerformDragAndDrop() { }
|
||||
|
||||
public virtual void UpdateDragAndDrop() { }
|
||||
|
||||
public virtual void DrawDragAndDropPreview() { }
|
||||
|
||||
public virtual void ExitDragAndDrop() { }
|
||||
|
||||
protected static Event e => Event.current;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class GraphContextExtensionAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public GraphContextExtensionAttribute(Type type)
|
||||
{
|
||||
Ensure.That(nameof(type)).IsNotNull(type);
|
||||
Ensure.That(nameof(type)).IsOfType(type, typeof(IGraphContext));
|
||||
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphContextExtensionProvider : MultiDecoratorProvider<IGraphContext, IGraphContextExtension, GraphContextExtensionAttribute>
|
||||
{
|
||||
static GraphContextExtensionProvider()
|
||||
{
|
||||
instance = new GraphContextExtensionProvider();
|
||||
}
|
||||
|
||||
public static GraphContextExtensionProvider instance { get; }
|
||||
}
|
||||
|
||||
public static class XCanvasExtensionProvider
|
||||
{
|
||||
public static IEnumerable<IGraphContextExtension> Extensions(this IGraphContext context)
|
||||
{
|
||||
return GraphContextExtensionProvider.instance.GetDecorators(context);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public struct GraphContextMenuItem
|
||||
{
|
||||
public Action<Vector2> action { get; }
|
||||
public string label { get; }
|
||||
|
||||
public GraphContextMenuItem(Action<Vector2> action, string label)
|
||||
{
|
||||
this.action = action;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphContextProvider : SingleDecoratorProvider<GraphReference, IGraphContext, GraphContextAttribute>
|
||||
{
|
||||
static GraphContextProvider()
|
||||
{
|
||||
instance = new GraphContextProvider();
|
||||
}
|
||||
|
||||
public static GraphContextProvider instance { get; }
|
||||
|
||||
protected override bool cache => true;
|
||||
|
||||
protected override Type GetDecoratedType(GraphReference reference)
|
||||
{
|
||||
return reference.graph.GetType();
|
||||
}
|
||||
|
||||
public override bool IsValid(GraphReference reference)
|
||||
{
|
||||
return reference.isValid;
|
||||
}
|
||||
}
|
||||
|
||||
public static class XGraphContextProvider
|
||||
{
|
||||
public static IGraphContext Context(this GraphReference reference)
|
||||
{
|
||||
return GraphContextProvider.instance.GetDecorator(reference);
|
||||
}
|
||||
|
||||
public static T Context<T>(this GraphReference reference) where T : IGraphContext
|
||||
{
|
||||
return GraphContextProvider.instance.GetDecorator<T>(reference);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class GraphSelection : ISet<IGraphElement>
|
||||
{
|
||||
public GraphSelection() : base()
|
||||
{
|
||||
set = new HashSet<IGraphElement>();
|
||||
}
|
||||
|
||||
public event Action changed;
|
||||
|
||||
private readonly HashSet<IGraphElement> set;
|
||||
|
||||
public int Count => set.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
private void OnChange()
|
||||
{
|
||||
changed?.Invoke();
|
||||
}
|
||||
|
||||
IEnumerator<IGraphElement> IEnumerable<IGraphElement>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public HashSet<IGraphElement>.Enumerator GetEnumerator()
|
||||
{
|
||||
return set.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Select(IGraphElement item)
|
||||
{
|
||||
Clear();
|
||||
Add(item);
|
||||
}
|
||||
|
||||
public void Select(IEnumerable<IGraphElement> items)
|
||||
{
|
||||
Clear();
|
||||
UnionWith(items);
|
||||
}
|
||||
|
||||
public bool Contains(IGraphElement item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
return set.Contains(item);
|
||||
}
|
||||
|
||||
public bool Add(IGraphElement item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
if (set.Add(item))
|
||||
{
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(IGraphElement item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
if (set.Remove(item))
|
||||
{
|
||||
OnChange();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (set.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
set.Clear();
|
||||
OnChange();
|
||||
}
|
||||
|
||||
#region Set Logic
|
||||
|
||||
public void ExceptWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.ExceptWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void IntersectWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.IntersectWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.SymmetricExceptWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
var countBefore = set.Count;
|
||||
|
||||
set.UnionWith(other);
|
||||
|
||||
if (countBefore != set.Count)
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.Overlaps(other);
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<IGraphElement> other)
|
||||
{
|
||||
return set.SetEquals(other);
|
||||
}
|
||||
|
||||
public int RemoveWhere(Predicate<IGraphElement> match)
|
||||
{
|
||||
return set.RemoveWhere(match);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ICollection
|
||||
|
||||
void ICollection<IGraphElement>.Add(IGraphElement item)
|
||||
{
|
||||
if (set.Add(item))
|
||||
{
|
||||
OnChange();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(IGraphElement[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var item in this)
|
||||
{
|
||||
array[i + arrayIndex] = item;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphContext : IDisposable
|
||||
{
|
||||
GraphReference reference { get; }
|
||||
|
||||
IEnumerable<IGraphContextExtension> extensions { get; }
|
||||
|
||||
IGraph graph { get; }
|
||||
|
||||
ICanvas canvas { get; }
|
||||
|
||||
GraphSelection selection { get; }
|
||||
|
||||
Metadata graphMetadata { get; }
|
||||
|
||||
Metadata selectionMetadata { get; }
|
||||
|
||||
Metadata ElementMetadata(IGraphElement element);
|
||||
|
||||
AnalyserProvider analyserProvider { get; }
|
||||
|
||||
string windowTitle { get; }
|
||||
|
||||
IEnumerable<ISidebarPanelContent> sidebarPanels { get; }
|
||||
|
||||
bool isPrefabInstance { get; }
|
||||
|
||||
void BeginEdit(bool disablePrefabInstance = true);
|
||||
|
||||
void EndEdit();
|
||||
|
||||
void DescribeAndAnalyze();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[InitializeAfterPlugins]
|
||||
public class GraphDebugDataProvider
|
||||
{
|
||||
static GraphDebugDataProvider()
|
||||
{
|
||||
GraphPointer.fetchRootDebugDataBinding = FetchRootDebugData;
|
||||
}
|
||||
|
||||
private static IGraphDebugData FetchRootDebugData(IGraphRoot root)
|
||||
{
|
||||
if (!rootDatas.TryGetValue(root, out var rootData))
|
||||
{
|
||||
rootData = new GraphDebugData(root.childGraph);
|
||||
rootDatas.Add(root, rootData);
|
||||
}
|
||||
|
||||
return rootData;
|
||||
}
|
||||
|
||||
private static Dictionary<IGraphRoot, IGraphDebugData> rootDatas = new Dictionary<IGraphRoot, IGraphDebugData>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,204 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class MultiDecoratorProvider<TDecorated, TDecorator, TAttribute>
|
||||
where TAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
protected MultiDecoratorProvider()
|
||||
{
|
||||
definedDecoratorTypes = new Dictionary<Type, HashSet<Type>>();
|
||||
resolvedDecoratorTypes = new Dictionary<Type, HashSet<Type>>();
|
||||
decorators = new Dictionary<TDecorated, HashSet<TDecorator>>();
|
||||
|
||||
foreach (var decoratorType in typeset.Where(t => t.HasAttribute<TAttribute>(false)))
|
||||
{
|
||||
foreach (var decoratedType in decoratorType.GetAttributes<TAttribute>(false).Select(a => a.type))
|
||||
{
|
||||
if (!definedDecoratorTypes.ContainsKey(decoratedType))
|
||||
{
|
||||
definedDecoratorTypes.Add(decoratedType, new HashSet<Type>());
|
||||
}
|
||||
|
||||
definedDecoratorTypes[decoratedType].Add(decoratorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<Type> typeset => Codebase.editorTypes;
|
||||
|
||||
private readonly Dictionary<TDecorated, HashSet<TDecorator>> decorators;
|
||||
protected readonly Dictionary<Type, HashSet<Type>> definedDecoratorTypes;
|
||||
private readonly Dictionary<Type, HashSet<Type>> resolvedDecoratorTypes;
|
||||
|
||||
protected virtual IEnumerable<TDecorator> CreateDecorators(TDecorated decorated)
|
||||
{
|
||||
return GetDecoratorTypes(decorated.GetType()).Select(t => (TDecorator)t.Instantiate(false, decorated));
|
||||
}
|
||||
|
||||
protected virtual bool ShouldInvalidateDecorators(TDecorated decorated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerable<TDecorator> GetDecorators(TDecorated decorated)
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
if (decorators.ContainsKey(decorated) && ShouldInvalidateDecorators(decorated))
|
||||
{
|
||||
foreach (var disposableDecorator in decorators[decorated].OfType<IDisposable>())
|
||||
{
|
||||
disposableDecorator.Dispose();
|
||||
}
|
||||
|
||||
decorators.Remove(decorated);
|
||||
}
|
||||
|
||||
if (!decorators.ContainsKey(decorated))
|
||||
{
|
||||
decorators.Add(decorated, CreateDecorators(decorated).ToHashSet());
|
||||
}
|
||||
|
||||
return decorators[decorated];
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TSpecificDecorator> GetDecorators<TSpecificDecorator>(TDecorated decorated) where TSpecificDecorator : TDecorator
|
||||
{
|
||||
return GetDecorators(decorated).OfType<TSpecificDecorator>();
|
||||
}
|
||||
|
||||
public virtual void ClearCache()
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
foreach (var decorator in decorators.SelectMany(kvp => kvp.Value).OfType<IDisposable>())
|
||||
{
|
||||
decorator.Dispose();
|
||||
}
|
||||
|
||||
decorators.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasDecorator(Type type)
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
if (!resolvedDecoratorTypes.ContainsKey(type))
|
||||
{
|
||||
resolvedDecoratorTypes.Add(type, ResolveDecoratorTypes(type).ToHashSet());
|
||||
}
|
||||
|
||||
return resolvedDecoratorTypes[type] != null;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Type> GetDecoratorTypes(Type type)
|
||||
{
|
||||
lock (decorators)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
if (!HasDecorator(type))
|
||||
{
|
||||
throw new NotSupportedException($"Cannot resolve decorator type for '{type}'.");
|
||||
}
|
||||
|
||||
return resolvedDecoratorTypes[type];
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<Type> ResolveDecoratorTypes(Type type)
|
||||
{
|
||||
return ResolveDecoratorTypesByHierarchy(type, true);
|
||||
}
|
||||
|
||||
protected IEnumerable<Type> ResolveDecoratorTypesByHierarchy(Type type, bool inherit)
|
||||
{
|
||||
// We traverse the tree recursively and manually instead of
|
||||
// using Linq to find any "assignable from" type in the defined
|
||||
// decorators list in order to preserve priority.
|
||||
|
||||
// For example, in an A : B : C chain where we have decorators
|
||||
// for B and C, this method will always map A to B, not A to C.
|
||||
|
||||
var resolved = DirectResolve(type) ?? GenericResolve(type);
|
||||
|
||||
if (resolved != null)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
if (inherit)
|
||||
{
|
||||
foreach (var baseTypeOrInterface in type.BaseTypeAndInterfaces(false))
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypesByHierarchy(baseTypeOrInterface, false);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (type.BaseType != null)
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypesByHierarchy(type.BaseType, true);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> DirectResolve(Type type)
|
||||
{
|
||||
if (definedDecoratorTypes.ContainsKey(type))
|
||||
{
|
||||
return definedDecoratorTypes[type];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GenericResolve(Type type)
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var typeDefinition = type.GetGenericTypeDefinition();
|
||||
|
||||
if (definedDecoratorTypes.ContainsKey(typeDefinition))
|
||||
{
|
||||
foreach (var definedDecoratorType in definedDecoratorTypes[typeDefinition])
|
||||
{
|
||||
// For example: [Decorator(List<>)] ListDecorator<T> gets passed T of the item
|
||||
if (definedDecoratorType.ContainsGenericParameters)
|
||||
{
|
||||
yield return definedDecoratorType.MakeGenericType(type.GetGenericArguments());
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return definedDecoratorType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class SingleDecoratorProvider<TDecorated, TDecorator, TAttribute>
|
||||
where TAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
protected readonly object typesLock = new object();
|
||||
protected readonly object instancesLock = new object();
|
||||
|
||||
protected SingleDecoratorProvider()
|
||||
{
|
||||
definedDecoratorTypes = new Dictionary<Type, Type>();
|
||||
resolvedDecoratorTypes = new Dictionary<Type, Type>();
|
||||
|
||||
decorators = new Dictionary<TDecorated, TDecorator>();
|
||||
decorateds = new Dictionary<TDecorator, TDecorated>();
|
||||
|
||||
MapAttributeTypes();
|
||||
|
||||
Freed();
|
||||
|
||||
EditorApplication.update += FreeIfNeeded;
|
||||
}
|
||||
|
||||
protected virtual TDecorator CreateDecorator(Type decoratorType, TDecorated decorated)
|
||||
{
|
||||
return (TDecorator)decoratorType.Instantiate(false, decorated);
|
||||
}
|
||||
|
||||
private TDecorator CreateDecorator(TDecorated decorated)
|
||||
{
|
||||
if (!IsValid(decorated))
|
||||
{
|
||||
throw new InvalidOperationException($"Decorated object is not valid: {decorated}");
|
||||
}
|
||||
|
||||
return CreateDecorator(GetDecoratorType(GetDecoratedType(decorated)), decorated);
|
||||
}
|
||||
|
||||
#region Type Resolution
|
||||
|
||||
// By restricting our search in editor types, we greatly reduce the enumeration
|
||||
// and therefore the attribute caching time on initialization
|
||||
protected virtual IEnumerable<Type> typeset => Codebase.editorTypes;
|
||||
|
||||
protected readonly Dictionary<Type, Type> definedDecoratorTypes;
|
||||
|
||||
protected readonly Dictionary<Type, Type> resolvedDecoratorTypes;
|
||||
|
||||
private void MapAttributeTypes()
|
||||
{
|
||||
foreach (var decoratorType in typeset.Where(t => t.HasAttribute<TAttribute>(false)))
|
||||
{
|
||||
foreach (var decoratedType in decoratorType.GetAttributes<TAttribute>(false).Select(a => a.type))
|
||||
{
|
||||
if (definedDecoratorTypes.ContainsKey(decoratedType))
|
||||
{
|
||||
Debug.LogWarning($"Multiple '{typeof(TDecorator)}' for '{decoratedType}'. Ignoring '{decoratorType}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
definedDecoratorTypes.Add(decoratedType, decoratorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasDecorator(Type decoratedType)
|
||||
{
|
||||
return TryGetDecoratorType(decoratedType, out var decoratorType);
|
||||
}
|
||||
|
||||
public bool TryGetDecoratorType(Type decoratedType, out Type decoratorType)
|
||||
{
|
||||
lock (typesLock)
|
||||
{
|
||||
Ensure.That(nameof(decoratedType)).IsNotNull(decoratedType);
|
||||
|
||||
if (!resolvedDecoratorTypes.TryGetValue(decoratedType, out decoratorType))
|
||||
{
|
||||
decoratorType = ResolveDecoratorType(decoratedType);
|
||||
resolvedDecoratorTypes.Add(decoratedType, decoratorType);
|
||||
}
|
||||
|
||||
return decoratorType != null;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type GetDecoratedType(TDecorated decorated)
|
||||
{
|
||||
return decorated.GetType();
|
||||
}
|
||||
|
||||
public Type GetDecoratorType(Type decoratedType)
|
||||
{
|
||||
lock (typesLock)
|
||||
{
|
||||
Ensure.That(nameof(decoratedType)).IsNotNull(decoratedType);
|
||||
|
||||
if (!TryGetDecoratorType(decoratedType, out var decoratorType))
|
||||
{
|
||||
throw new NotSupportedException($"Cannot decorate '{decoratedType}' with '{typeof(TDecorator)}'.");
|
||||
}
|
||||
|
||||
return decoratorType;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Type ResolveDecoratorType(Type decoratedType)
|
||||
{
|
||||
return ResolveDecoratorTypeByHierarchy(decoratedType);
|
||||
}
|
||||
|
||||
protected Type ResolveDecoratorTypeByHierarchy(Type decoratedType, bool inherit = true)
|
||||
{
|
||||
// We traverse the tree recursively and manually instead of
|
||||
// using Linq to find any "assignable from" type in the defined
|
||||
// decorators list in order to preserve priority.
|
||||
|
||||
// For example, in an A : B : C chain where we have decorators
|
||||
// for B and C, this method will always map A to B, not A to C.
|
||||
|
||||
var resolved = DirectResolve(decoratedType) ?? GenericResolve(decoratedType);
|
||||
|
||||
if (resolved != null)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
if (inherit)
|
||||
{
|
||||
foreach (var baseTypeOrInterface in decoratedType.BaseTypeAndInterfaces(false))
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypeByHierarchy(baseTypeOrInterface, false);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
|
||||
if (decoratedType.BaseType != null)
|
||||
{
|
||||
var baseResolved = ResolveDecoratorTypeByHierarchy(decoratedType.BaseType);
|
||||
|
||||
if (baseResolved != null)
|
||||
{
|
||||
return baseResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Type DirectResolve(Type decoratedType)
|
||||
{
|
||||
if (definedDecoratorTypes.ContainsKey(decoratedType))
|
||||
{
|
||||
var definedDecoratorType = definedDecoratorTypes[decoratedType];
|
||||
|
||||
if (definedDecoratorType.IsGenericTypeDefinition)
|
||||
{
|
||||
var arguments = definedDecoratorType.GetGenericArguments();
|
||||
|
||||
// For example: [Decorator(Decorated)] Decorator<TDecorated> gets properly closed-constructed with type
|
||||
if (arguments.Length == 1 && arguments[0].CanMakeGenericTypeVia(decoratedType))
|
||||
{
|
||||
return definedDecoratorType.MakeGenericType(decoratedType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return definedDecoratorTypes[decoratedType];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Type GenericResolve(Type decoratedType)
|
||||
{
|
||||
if (decoratedType.IsGenericType)
|
||||
{
|
||||
var typeDefinition = decoratedType.GetGenericTypeDefinition();
|
||||
|
||||
if (definedDecoratorTypes.ContainsKey(typeDefinition))
|
||||
{
|
||||
var definedDecoratorType = definedDecoratorTypes[typeDefinition];
|
||||
|
||||
// For example: [Decorator(List<>)] ListDecorator<T> gets passed T of the item
|
||||
if (definedDecoratorType.ContainsGenericParameters)
|
||||
{
|
||||
return definedDecoratorType.MakeGenericType(decoratedType.GetGenericArguments());
|
||||
}
|
||||
else
|
||||
{
|
||||
return definedDecoratorType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Cache
|
||||
|
||||
protected readonly Dictionary<TDecorated, TDecorator> decorators;
|
||||
|
||||
protected readonly Dictionary<TDecorator, TDecorated> decorateds;
|
||||
|
||||
protected abstract bool cache { get; }
|
||||
|
||||
public abstract bool IsValid(TDecorated decorated);
|
||||
|
||||
public TDecorator GetDecorator(TDecorated decorated)
|
||||
{
|
||||
Ensure.That(nameof(decorated)).IsNotNull(decorated);
|
||||
|
||||
if (!cache)
|
||||
{
|
||||
var decorator = CreateDecorator(decorated);
|
||||
(decorator as IInitializable)?.Initialize();
|
||||
return decorator;
|
||||
}
|
||||
|
||||
lock (instancesLock)
|
||||
{
|
||||
var decoratorExists = decorators.TryGetValue(decorated, out var decorator);
|
||||
|
||||
if (decoratorExists && !IsValid(decorateds[decorator]))
|
||||
{
|
||||
Free(decorator);
|
||||
|
||||
decoratorExists = false;
|
||||
}
|
||||
|
||||
if (!decoratorExists)
|
||||
{
|
||||
decorator = CreateDecorator(decorated);
|
||||
|
||||
decorators.Add(decorated, decorator);
|
||||
decorateds.Add(decorator, decorated);
|
||||
|
||||
(decorator as IInitializable)?.Initialize();
|
||||
}
|
||||
|
||||
return decorator;
|
||||
}
|
||||
}
|
||||
|
||||
public T GetDecorator<T>(TDecorated decorated) where T : TDecorator
|
||||
{
|
||||
var decorator = GetDecorator(decorated);
|
||||
|
||||
if (!(decorator is T))
|
||||
{
|
||||
throw new InvalidCastException($"Decorator type mismatch for '{decorated}': found {decorator.GetType()}, expected {typeof(T)}.");
|
||||
}
|
||||
|
||||
return (T)decorator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Collection
|
||||
|
||||
private DateTime lastFreeTime;
|
||||
|
||||
protected virtual TimeSpan freeInterval => TimeSpan.FromSeconds(5);
|
||||
|
||||
private void Freed()
|
||||
{
|
||||
lastFreeTime = DateTime.Now;
|
||||
}
|
||||
|
||||
private bool shouldFree => cache && DateTime.Now > lastFreeTime + freeInterval;
|
||||
|
||||
private void FreeIfNeeded()
|
||||
{
|
||||
if (shouldFree)
|
||||
{
|
||||
FreeInvalid();
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(TDecorator decorator)
|
||||
{
|
||||
lock (instancesLock)
|
||||
{
|
||||
if (decorateds.ContainsKey(decorator))
|
||||
{
|
||||
(decorator as IDisposable)?.Dispose();
|
||||
var decorated = decorateds[decorator];
|
||||
decorateds.Remove(decorator);
|
||||
decorators.Remove(decorated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Free(IEnumerable<TDecorator> decorators)
|
||||
{
|
||||
foreach (var decorator in decorators)
|
||||
{
|
||||
Free(decorator);
|
||||
}
|
||||
}
|
||||
|
||||
public void FreeInvalid()
|
||||
{
|
||||
if (!cache)
|
||||
{
|
||||
Debug.LogWarning($"Trying to free a decorator provider without caching: {this}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (instancesLock)
|
||||
{
|
||||
Free(decorators.Where(d => !IsValid(d.Key)).Select(d => d.Value).ToArray());
|
||||
Freed();
|
||||
}
|
||||
}
|
||||
|
||||
public void FreeAll()
|
||||
{
|
||||
if (!cache)
|
||||
{
|
||||
Debug.LogWarning($"Trying to free a decorator provider without caching: {this}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lock (instancesLock)
|
||||
{
|
||||
Free(decorators.Values.ToArray());
|
||||
Freed();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Renew<TSpecificDecorator>(ref TSpecificDecorator decorator, TDecorated decorated, Func<TDecorated, TSpecificDecorator> constructor = null) where TSpecificDecorator : TDecorator
|
||||
{
|
||||
if (decorator == null || !IsValid(decorated))
|
||||
{
|
||||
if (constructor != null)
|
||||
{
|
||||
decorator = constructor(decorated);
|
||||
}
|
||||
else
|
||||
{
|
||||
decorator = (TSpecificDecorator)CreateDecorator(typeof(TSpecificDecorator), decorated);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory methods that create <see cref="IElementAdderMenuBuilder{TContext}" />
|
||||
/// instances that can then be used to build element adder menus.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <para>
|
||||
/// The following example demonstrates how to build and display a menu which
|
||||
/// allows the user to add elements to a given context object upon clicking a button:
|
||||
/// </para>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// public class ShoppingListElementAdder : IElementAdder<ShoppingList> {
|
||||
/// public ShoppingListElementAdder(ShoppingList shoppingList) {
|
||||
/// Object = shoppingList;
|
||||
/// }
|
||||
///
|
||||
/// public ShoppingList Object { get; private set; }
|
||||
///
|
||||
/// public bool CanAddElement(Type type) {
|
||||
/// return true;
|
||||
/// }
|
||||
/// public object AddElement(Type type) {
|
||||
/// var instance = Activator.CreateInstance(type);
|
||||
/// shoppingList.Add((ShoppingItem)instance);
|
||||
/// return instance;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// private void DrawAddMenuButton(ShoppingList shoppingList) {
|
||||
/// var content = new GUIContent("Add Menu");
|
||||
/// Rect position = GUILayoutUtility.GetRect(content, GUI.skin.button);
|
||||
/// if (GUI.Button(position, content)) {
|
||||
/// var builder = ElementAdderMenuBuilder.For<ShoppingList>(ShoppingItem);
|
||||
/// builder.SetElementAdder(new ShoppingListElementAdder(shoppingList));
|
||||
/// var menu = builder.GetMenu();
|
||||
/// menu.DropDown(buttonPosition);
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// public class ShoppingListElementAdder extends IElementAdder.<ShoppingList> {
|
||||
/// var _object:ShoppingList;
|
||||
///
|
||||
/// function ShoppingListElementAdder(shoppingList:ShoppingList) {
|
||||
/// Object = shoppingList;
|
||||
/// }
|
||||
///
|
||||
/// function get Object():ShoppingList { return _object; }
|
||||
///
|
||||
/// function CanAddElement(type:Type):boolean {
|
||||
/// return true;
|
||||
/// }
|
||||
/// function AddElement(type:Type):System.Object {
|
||||
/// var instance = Activator.CreateInstance(type);
|
||||
/// shoppingList.Add((ShoppingItem)instance);
|
||||
/// return instance;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// function DrawAddMenuButton(shoppingList:ShoppingList) {
|
||||
/// var content = new GUIContent('Add Menu');
|
||||
/// var position = GUILayoutUtility.GetRect(content, GUI.skin.button);
|
||||
/// if (GUI.Button(position, content)) {
|
||||
/// var builder = ElementAdderMenuBuilder.For.<ShoppingList>(ShoppingItem);
|
||||
/// builder.SetElementAdder(new ShoppingListElementAdder(shoppingList));
|
||||
/// var menu = builder.GetMenu();
|
||||
/// menu.DropDown(buttonPosition);
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
public static class ElementAdderMenuBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IElementAdderMenuBuilder{TContext}" /> to build an element
|
||||
/// adder menu for a context object of the type <typeparamref name="TContext" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <returns>
|
||||
/// A new <see cref="IElementAdderMenuBuilder{TContext}" /> instance.
|
||||
/// </returns>
|
||||
/// <seealso cref="IElementAdderMenuBuilder{TContext}.SetContractType(Type)" />
|
||||
public static IElementAdderMenuBuilder<TContext> For<TContext>()
|
||||
{
|
||||
return new GenericElementAdderMenuBuilder<TContext>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IElementAdderMenuBuilder{TContext}" /> to build an element
|
||||
/// adder menu for a context object of the type <typeparamref name="TContext" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// A new <see cref="IElementAdderMenuBuilder{TContext}" /> instance.
|
||||
/// </returns>
|
||||
/// <seealso cref="IElementAdderMenuBuilder{TContext}.SetContractType(Type)" />
|
||||
public static IElementAdderMenuBuilder<TContext> For<TContext>(Type contractType)
|
||||
{
|
||||
var builder = For<TContext>();
|
||||
builder.SetContractType(contractType);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Annotate <see cref="IElementAdderMenuCommand{TContext}" /> implementations with a
|
||||
/// <see cref="ElementAdderMenuCommandAttribute" /> to associate it with the contract
|
||||
/// type of addable elements.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <para>
|
||||
/// The following source code demonstrates how to add a helper menu command to
|
||||
/// the add element menu of a shopping list:
|
||||
/// </para>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// [ElementAdderMenuCommand(typeof(ShoppingItem))]
|
||||
/// public class AddFavoriteShoppingItemsCommand : IElementAdderMenuCommand<ShoppingList> {
|
||||
/// public AddFavoriteShoppingItemsCommand() {
|
||||
/// Content = new GUIContent("Add Favorite Items");
|
||||
/// }
|
||||
///
|
||||
/// public GUIContent Content { get; private set; }
|
||||
///
|
||||
/// public bool CanExecute(IElementAdder<ShoppingList> elementAdder) {
|
||||
/// return true;
|
||||
/// }
|
||||
/// public void Execute(IElementAdder<ShoppingList> elementAdder) {
|
||||
/// // xTODO: Add favorite items to the shopping list!
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// @ElementAdderMenuCommand(ShoppingItem)
|
||||
/// class AddFavoriteShoppingItemsCommand extends IElementAdderMenuCommand.<ShoppingList> {
|
||||
/// var _content:GUIContent = new GUIContent('Add Favorite Items');
|
||||
///
|
||||
/// function get Content():GUIContent { return _content; }
|
||||
///
|
||||
/// function CanExecute(elementAdder:IElementAdder.<ShoppingList>):boolean {
|
||||
/// return true;
|
||||
/// }
|
||||
/// function Execute(elementAdder:IElementAdder.<ShoppingList>) {
|
||||
/// // xTODO: Add favorite items to the shopping list!
|
||||
/// }
|
||||
/// }
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class ElementAdderMenuCommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ElementAdderMenuCommandAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
public ElementAdderMenuCommandAttribute(Type contractType)
|
||||
{
|
||||
ContractType = contractType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contract type of addable elements.
|
||||
/// </summary>
|
||||
public Type ContractType { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides meta information which is useful when creating new implementations of
|
||||
/// the <see cref="IElementAdderMenuBuilder{TContext}" /> interface.
|
||||
/// </summary>
|
||||
public static class ElementAdderMeta
|
||||
{
|
||||
#region Adder Menu Command Types
|
||||
|
||||
private static Dictionary<Type, Dictionary<Type, List<Type>>> s_ContextMap = new Dictionary<Type, Dictionary<Type, List<Type>>>();
|
||||
|
||||
private static IEnumerable<Type> GetMenuCommandTypes<TContext>()
|
||||
{
|
||||
return
|
||||
from a in AppDomain.CurrentDomain.GetAssemblies()
|
||||
from t in a.GetTypesSafely()
|
||||
where t.IsClass && !t.IsAbstract && t.IsDefined(typeof(ElementAdderMenuCommandAttribute), false)
|
||||
where typeof(IElementAdderMenuCommand<TContext>).IsAssignableFrom(t)
|
||||
select t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of the <see cref="IElementAdderMenuCommand{TContext}" /> types
|
||||
/// that are associated with the specified <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// An array containing zero or more <see cref="System.Type" />.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetMenuCommands{TContext}(Type)" />
|
||||
public static Type[] GetMenuCommandTypes<TContext>(Type contractType)
|
||||
{
|
||||
if (contractType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contractType));
|
||||
}
|
||||
|
||||
Dictionary<Type, List<Type>> contractMap;
|
||||
List<Type> commandTypes;
|
||||
if (s_ContextMap.TryGetValue(typeof(TContext), out contractMap))
|
||||
{
|
||||
if (contractMap.TryGetValue(contractType, out commandTypes))
|
||||
{
|
||||
return commandTypes.ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
contractMap = new Dictionary<Type, List<Type>>();
|
||||
s_ContextMap[typeof(TContext)] = contractMap;
|
||||
}
|
||||
|
||||
commandTypes = new List<Type>();
|
||||
|
||||
foreach (var commandType in GetMenuCommandTypes<TContext>())
|
||||
{
|
||||
var attributes = (ElementAdderMenuCommandAttribute[])Attribute.GetCustomAttributes(commandType, typeof(ElementAdderMenuCommandAttribute));
|
||||
if (!attributes.Any(a => a.ContractType == contractType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
commandTypes.Add(commandType);
|
||||
}
|
||||
|
||||
contractMap[contractType] = commandTypes;
|
||||
return commandTypes.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of <see cref="IElementAdderMenuCommand{TContext}" /> instances
|
||||
/// that are associated with the specified <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// An array containing zero or more <see cref="IElementAdderMenuCommand{TContext}" /> instances.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetMenuCommandTypes{TContext}(Type)" />
|
||||
public static IElementAdderMenuCommand<TContext>[] GetMenuCommands<TContext>(Type contractType)
|
||||
{
|
||||
var commandTypes = GetMenuCommandTypes<TContext>(contractType);
|
||||
var commands = new IElementAdderMenuCommand<TContext>[commandTypes.Length];
|
||||
for (var i = 0; i < commandTypes.Length; ++i)
|
||||
{
|
||||
commands[i] = (IElementAdderMenuCommand<TContext>)Activator.CreateInstance(commandTypes[i]);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Concrete Element Types
|
||||
|
||||
private static Dictionary<Type, Type[]> s_ConcreteElementTypes = new Dictionary<Type, Type[]>();
|
||||
|
||||
private static IEnumerable<Type> GetConcreteElementTypesHelper(Type contractType)
|
||||
{
|
||||
if (contractType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contractType));
|
||||
}
|
||||
|
||||
Type[] concreteTypes;
|
||||
if (!s_ConcreteElementTypes.TryGetValue(contractType, out concreteTypes))
|
||||
{
|
||||
concreteTypes =
|
||||
(from a in AppDomain.CurrentDomain.GetAssemblies()
|
||||
from t in a.GetTypesSafely()
|
||||
where t.IsClass && !t.IsAbstract && contractType.IsAssignableFrom(t)
|
||||
orderby t.Name
|
||||
select t
|
||||
).ToArray();
|
||||
s_ConcreteElementTypes[contractType] = concreteTypes;
|
||||
}
|
||||
|
||||
return concreteTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a filtered array of the concrete element types that implement the
|
||||
/// specified <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A type is excluded from the resulting array when one or more of the
|
||||
/// specified <paramref name="filters" /> returns a value of <c>false</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <param name="filters">An array of zero or more filters.</param>
|
||||
/// <returns>
|
||||
/// An array of zero or more concrete element types.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetConcreteElementTypes(Type)" />
|
||||
public static Type[] GetConcreteElementTypes(Type contractType, Func<Type, bool>[] filters)
|
||||
{
|
||||
return
|
||||
(from t in GetConcreteElementTypesHelper(contractType)
|
||||
where IsTypeIncluded(t, filters)
|
||||
select t
|
||||
).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of all the concrete element types that implement the specified
|
||||
/// <paramref name="contractType" />.
|
||||
/// </summary>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
/// <returns>
|
||||
/// An array of zero or more concrete element types.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="contractType" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
/// <seealso cref="GetConcreteElementTypes(Type, Func{Type, bool}[])" />
|
||||
public static Type[] GetConcreteElementTypes(Type contractType)
|
||||
{
|
||||
return GetConcreteElementTypesHelper(contractType).ToArray();
|
||||
}
|
||||
|
||||
private static bool IsTypeIncluded(Type concreteType, Func<Type, bool>[] filters)
|
||||
{
|
||||
if (filters != null)
|
||||
{
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
if (!filter(concreteType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
internal sealed class GenericElementAdderMenu : IElementAdderMenu
|
||||
{
|
||||
private GenericMenu _innerMenu = new GenericMenu();
|
||||
|
||||
public bool IsEmpty => _innerMenu.GetItemCount() == 0;
|
||||
|
||||
public void AddItem(GUIContent content, GenericMenu.MenuFunction handler)
|
||||
{
|
||||
_innerMenu.AddItem(content, false, handler);
|
||||
}
|
||||
|
||||
public void AddDisabledItem(GUIContent content)
|
||||
{
|
||||
_innerMenu.AddDisabledItem(content);
|
||||
}
|
||||
|
||||
public void AddSeparator(string path = "")
|
||||
{
|
||||
_innerMenu.AddSeparator(path);
|
||||
}
|
||||
|
||||
public void DropDown(Rect position)
|
||||
{
|
||||
_innerMenu.DropDown(position);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
internal sealed class GenericElementAdderMenuBuilder<TContext> : IElementAdderMenuBuilder<TContext>
|
||||
{
|
||||
public GenericElementAdderMenuBuilder()
|
||||
{
|
||||
_typeDisplayNameFormatter = NicifyTypeName;
|
||||
}
|
||||
|
||||
private Type _contractType;
|
||||
private IElementAdder<TContext> _elementAdder;
|
||||
private Func<Type, string> _typeDisplayNameFormatter;
|
||||
private List<Func<Type, bool>> _typeFilters = new List<Func<Type, bool>>();
|
||||
private List<IElementAdderMenuCommand<TContext>> _customCommands = new List<IElementAdderMenuCommand<TContext>>();
|
||||
|
||||
public void SetContractType(Type contractType)
|
||||
{
|
||||
_contractType = contractType;
|
||||
}
|
||||
|
||||
public void SetElementAdder(IElementAdder<TContext> elementAdder)
|
||||
{
|
||||
_elementAdder = elementAdder;
|
||||
}
|
||||
|
||||
public void SetTypeDisplayNameFormatter(Func<Type, string> formatter)
|
||||
{
|
||||
_typeDisplayNameFormatter = formatter ?? NicifyTypeName;
|
||||
}
|
||||
|
||||
public void AddTypeFilter(Func<Type, bool> typeFilter)
|
||||
{
|
||||
if (typeFilter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(typeFilter));
|
||||
}
|
||||
|
||||
_typeFilters.Add(typeFilter);
|
||||
}
|
||||
|
||||
public void AddCustomCommand(IElementAdderMenuCommand<TContext> command)
|
||||
{
|
||||
if (command == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(command));
|
||||
}
|
||||
|
||||
_customCommands.Add(command);
|
||||
}
|
||||
|
||||
public IElementAdderMenu GetMenu()
|
||||
{
|
||||
var menu = new GenericElementAdderMenu();
|
||||
|
||||
AddCommandsToMenu(menu, _customCommands);
|
||||
|
||||
if (_contractType != null)
|
||||
{
|
||||
AddCommandsToMenu(menu, ElementAdderMeta.GetMenuCommands<TContext>(_contractType));
|
||||
AddConcreteTypesToMenu(menu, ElementAdderMeta.GetConcreteElementTypes(_contractType, _typeFilters.ToArray()));
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
private void AddCommandsToMenu(GenericElementAdderMenu menu, IList<IElementAdderMenuCommand<TContext>> commands)
|
||||
{
|
||||
if (commands.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.IsEmpty)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
}
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
if (_elementAdder != null && command.CanExecute(_elementAdder))
|
||||
{
|
||||
menu.AddItem(command.Content, () => command.Execute(_elementAdder));
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(command.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddConcreteTypesToMenu(GenericElementAdderMenu menu, Type[] concreteTypes)
|
||||
{
|
||||
if (concreteTypes.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.IsEmpty)
|
||||
{
|
||||
menu.AddSeparator();
|
||||
}
|
||||
|
||||
foreach (var concreteType in concreteTypes)
|
||||
{
|
||||
var content = new GUIContent(_typeDisplayNameFormatter(concreteType));
|
||||
if (_elementAdder != null && _elementAdder.CanAddElement(concreteType))
|
||||
{
|
||||
menu.AddItem(content, () =>
|
||||
{
|
||||
if (_elementAdder.CanAddElement(concreteType))
|
||||
{
|
||||
_elementAdder.AddElement(concreteType);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
menu.AddDisabledItem(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NicifyTypeName(Type type)
|
||||
{
|
||||
return ObjectNames.NicifyVariableName(type.Name);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an object which adds elements to a context object of the type
|
||||
/// <typeparamref name="TContext" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
public interface IElementAdder<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the context object.
|
||||
/// </summary>
|
||||
TContext Object { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a new element of the specified <paramref name="type" /> can
|
||||
/// be added to the associated context object.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of element to add.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if an element of the specified type can be added;
|
||||
/// otherwise, a value of <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanAddElement(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an element of the specified <paramref name="type" /> to the associated
|
||||
/// context object.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of element to add.</param>
|
||||
/// <returns>
|
||||
/// The new element.
|
||||
/// </returns>
|
||||
object AddElement(Type type);
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a menu interface.
|
||||
/// </summary>
|
||||
public interface IElementAdderMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the menu contains any items.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the menu contains one or more items; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
bool IsEmpty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Displays the drop-down menu inside an editor GUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method should only be used during <b>OnGUI</b> and <b>OnSceneGUI</b>
|
||||
/// events; for instance, inside an editor window, a custom inspector or scene view.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position of menu button in the GUI.</param>
|
||||
void DropDown(Rect position);
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for building an <see cref="IElementAdderMenu" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
public interface IElementAdderMenuBuilder<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets contract type of the elements that can be included in the <see cref="IElementAdderMenu" />.
|
||||
/// Only non-abstract class types that are assignable from the <paramref name="contractType" />
|
||||
/// will be included in the built menu.
|
||||
/// </summary>
|
||||
/// <param name="contractType">Contract type of addable elements.</param>
|
||||
void SetContractType(Type contractType);
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="IElementAdder{TContext}" /> implementation which is used
|
||||
/// when adding new elements to the context object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Specify a value of <c>null</c> for <paramref name="elementAdder" /> to
|
||||
/// prevent the selection of any types.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="elementAdder">Element adder.</param>
|
||||
void SetElementAdder(IElementAdder<TContext> elementAdder);
|
||||
|
||||
/// <summary>
|
||||
/// Set the function that formats the display of type names in the user interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Specify a value of <c>null</c> for <paramref name="formatter" /> to
|
||||
/// assume the default formatting function.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="formatter">Function that formats display name of type; or <c>null</c>.</param>
|
||||
void SetTypeDisplayNameFormatter(Func<Type, string> formatter);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a filter function which determines whether types can be included or
|
||||
/// whether they need to be excluded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If a filter function returns a value of <c>false</c> then that type
|
||||
/// will not be included in the menu interface.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="typeFilter">Filter function.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="typeFilter" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
void AddTypeFilter(Func<Type, bool> typeFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom command to the menu.
|
||||
/// </summary>
|
||||
/// <param name="command">The custom command.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// If <paramref name="command" /> is <c>null</c>.
|
||||
/// </exception>
|
||||
void AddCustomCommand(IElementAdderMenuCommand<TContext> command);
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns a new <see cref="IElementAdderMenu" /> instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new <see cref="IElementAdderMenu" /> instance each time the method is invoked.
|
||||
/// </returns>
|
||||
IElementAdderMenu GetMenu();
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Element_Adder_Menu
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for a menu command that can be included in an <see cref="IElementAdderMenu" />
|
||||
/// either by annotating an implementation of the <see cref="IElementAdderMenuCommand{TContext}" />
|
||||
/// interface with <see cref="ElementAdderMenuCommandAttribute" /> or directly by
|
||||
/// calling <see cref="IElementAdderMenuBuilder{TContext}.AddCustomCommand" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="TContext">Type of the context object that elements can be added to.</typeparam>
|
||||
public interface IElementAdderMenuCommand<TContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the content of the menu command.
|
||||
/// </summary>
|
||||
GUIContent Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the command can be executed.
|
||||
/// </summary>
|
||||
/// <param name="elementAdder">
|
||||
/// The associated element adder provides access to
|
||||
/// the <typeparamref name="TContext" /> instance.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if the command can execute; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanExecute(IElementAdder<TContext> elementAdder);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command.
|
||||
/// </summary>
|
||||
/// <param name="elementAdder">
|
||||
/// The associated element adder provides access to
|
||||
/// the <typeparamref name="TContext" /> instance.
|
||||
/// </param>
|
||||
void Execute(IElementAdder<TContext> elementAdder);
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Reorderable list adaptor for generic list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This adaptor can be subclassed to add special logic to item height calculation.
|
||||
/// You may want to implement a custom adaptor class where specialised functionality
|
||||
/// is needed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// List elements which implement the <see cref="System.ICloneable" /> interface are
|
||||
/// cloned using that interface upon duplication; otherwise the item value or reference is
|
||||
/// simply copied.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">Type of list element.</typeparam>
|
||||
public class GenericListAdaptor<T> : IReorderableListAdaptor
|
||||
{
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="GenericListAdaptor{T}" />.
|
||||
/// </summary>
|
||||
/// <param name="list">The list which can be reordered.</param>
|
||||
/// <param name="itemDrawer">Callback to draw list item.</param>
|
||||
/// <param name="itemHeight">Height of list item in pixels.</param>
|
||||
public GenericListAdaptor(IList<T> list, ReorderableListControl.ItemDrawer<T> itemDrawer, float itemHeight)
|
||||
{
|
||||
_list = list;
|
||||
_itemDrawer = itemDrawer ?? ReorderableListGUI.DefaultItemDrawer;
|
||||
FixedItemHeight = itemHeight;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private IList<T> _list;
|
||||
|
||||
private ReorderableListControl.ItemDrawer<T> _itemDrawer;
|
||||
|
||||
/// <summary>
|
||||
/// Fixed height of each list item.
|
||||
/// </summary>
|
||||
public float FixedItemHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets element from list.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of element.</param>
|
||||
/// <returns>
|
||||
/// The element.
|
||||
/// </returns>
|
||||
public T this[int index] => _list[index];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying list data structure.
|
||||
/// </summary>
|
||||
public IList<T> List => _list;
|
||||
|
||||
#region IReorderableListAdaptor - Implementation
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _list.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanDrag(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanRemove(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Add()
|
||||
{
|
||||
_list.Add(default(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Insert(int index)
|
||||
{
|
||||
_list.Insert(index, default(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Duplicate(int index)
|
||||
{
|
||||
var newItem = _list[index];
|
||||
|
||||
var existingItem = newItem as ICloneable;
|
||||
if (existingItem != null)
|
||||
{
|
||||
newItem = (T)existingItem.Clone();
|
||||
}
|
||||
|
||||
_list.Insert(index + 1, newItem);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Remove(int index)
|
||||
{
|
||||
_list.RemoveAt(index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Move(int sourceIndex, int destIndex)
|
||||
{
|
||||
if (destIndex > sourceIndex)
|
||||
{
|
||||
--destIndex;
|
||||
}
|
||||
|
||||
var item = _list[sourceIndex];
|
||||
_list.RemoveAt(sourceIndex);
|
||||
_list.Insert(destIndex, item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Clear()
|
||||
{
|
||||
_list.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void BeginGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void EndGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItemBackground(Rect position, int index) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItem(Rect position, int index)
|
||||
{
|
||||
_list[index] = _itemDrawer(position, _list[index]);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual float GetItemHeight(int index)
|
||||
{
|
||||
return FixedItemHeight;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Adaptor allowing reorderable list control to interface with list data.
|
||||
/// </summary>
|
||||
/// <see cref="IReorderableListDropTarget" />
|
||||
public interface IReorderableListAdaptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets count of elements in list.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an item can be reordered by dragging mouse.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This should be a light-weight method since it will be used to determine
|
||||
/// whether grab handle should be included for each item in a reorderable list.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Please note that returning a value of <c>false</c> does not prevent movement
|
||||
/// on list item since other draggable items can be moved around it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="index">Zero-based index for list element.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if item can be dragged; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanDrag(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an item can be removed from list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This should be a light-weight method since it will be used to determine
|
||||
/// whether remove button should be included for each item in list.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This is redundant when <see cref="ReorderableListFlags.HideRemoveButtons" />
|
||||
/// is specified.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="index">Zero-based index for list element.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if item can be removed; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
bool CanRemove(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Add new element at end of list.
|
||||
/// </summary>
|
||||
void Add();
|
||||
|
||||
/// <summary>
|
||||
/// Insert new element at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index for list element.</param>
|
||||
void Insert(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate existing element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Consider using the <see cref="System.ICloneable" /> interface to
|
||||
/// duplicate list elements where appropriate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="index">Zero-based index of list element.</param>
|
||||
void Duplicate(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Remove element at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of list element.</param>
|
||||
void Remove(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Move element from source index to destination index.
|
||||
/// </summary>
|
||||
/// <param name="sourceIndex">Zero-based index of source element.</param>
|
||||
/// <param name="destIndex">Zero-based index of destination element.</param>
|
||||
void Move(int sourceIndex, int destIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Clear all elements from list.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before any list items are drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is only used to handle GUI repaint events.</para>
|
||||
/// </remarks>
|
||||
/// <see cref="EndGUI()" />
|
||||
void BeginGUI();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after all list items have been drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is only used to handle GUI repaint events.</para>
|
||||
/// </remarks>
|
||||
/// <see cref="BeginGUI()" />
|
||||
void EndGUI();
|
||||
|
||||
/// <summary>
|
||||
/// Draws background of a list item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is only used to handle GUI repaint events.</para>
|
||||
/// <para>
|
||||
/// Background of list item spans a slightly larger area than the main
|
||||
/// interface that is drawn by <see cref="DrawItem(Rect, int)" /> since it is
|
||||
/// drawn behind the grab handle.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Total position of list element in GUI.</param>
|
||||
/// <param name="index">Zero-based index of array element.</param>
|
||||
void DrawItemBackground(Rect position, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Draws main interface for a list item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is used to handle all GUI events.</para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position in GUI.</param>
|
||||
/// <param name="index">Zero-based index of array element.</param>
|
||||
void DrawItem(Rect position, int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets height of list item in pixels.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of array element.</param>
|
||||
/// <returns>
|
||||
/// Measurement in pixels.
|
||||
/// </returns>
|
||||
float GetItemHeight(int index);
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Can be implemented along with <see cref="IReorderableListAdaptor" /> when drop
|
||||
/// insertion or ordering is desired.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type of "drop" functionality can occur when the "drag" phase of the
|
||||
/// drag and drop operation was initiated elsewhere. For example, a custom
|
||||
/// <see cref="IReorderableListAdaptor" /> could insert entirely new items by
|
||||
/// dragging and dropping from the Unity "Project" window.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <see cref="IReorderableListAdaptor" />
|
||||
public interface IReorderableListDropTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether an item is being dragged and that it can be inserted
|
||||
/// or moved by dropping somewhere into the reorderable list control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is always called whilst drawing an editor GUI.</para>
|
||||
/// </remarks>
|
||||
/// <param name="insertionIndex">Zero-based index of insertion.</param>
|
||||
/// <returns>
|
||||
/// A value of <c>true</c> if item can be dropped; otherwise <c>false</c>.
|
||||
/// </returns>
|
||||
/// <see cref="UnityEditor.DragAndDrop" />
|
||||
bool CanDropInsert(int insertionIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Processes the current drop insertion operation when <see cref="CanDropInsert(int)" />
|
||||
/// returns a value of <c>true</c> to process, accept or cancel.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This method is always called whilst drawing an editor GUI.</para>
|
||||
/// <para>
|
||||
/// This method is only called when <see cref="CanDropInsert(int)" />
|
||||
/// returns a value of <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="insertionIndex">Zero-based index of insertion.</param>
|
||||
/// <see cref="ReorderableListGUI.CurrentListControlID" />
|
||||
/// <see cref="UnityEditor.DragAndDrop" />
|
||||
void ProcessDropInsertion(int insertionIndex);
|
||||
}
|
||||
}
|
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functions to assist with GUIs.
|
||||
/// </summary>
|
||||
/// <exclude />
|
||||
public static class GUIHelper
|
||||
{
|
||||
static GUIHelper()
|
||||
{
|
||||
var tyGUIClip = typeof(GUIUtility).Assembly.GetType("UnityEngine.GUIClip", true);
|
||||
if (tyGUIClip != null)
|
||||
{
|
||||
var piVisibleRect = tyGUIClip.GetProperty("visibleRect", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (piVisibleRect != null)
|
||||
{
|
||||
VisibleRect = (Func<Rect>)Delegate.CreateDelegate(typeof(Func<Rect>), piVisibleRect.GetGetMethod(true));
|
||||
}
|
||||
}
|
||||
|
||||
var miFocusTextInControl = typeof(EditorGUI).GetMethod("FocusTextInControl", BindingFlags.Static | BindingFlags.Public);
|
||||
if (miFocusTextInControl == null)
|
||||
{
|
||||
miFocusTextInControl = typeof(GUI).GetMethod("FocusControl", BindingFlags.Static | BindingFlags.Public);
|
||||
}
|
||||
|
||||
FocusTextInControl = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), miFocusTextInControl);
|
||||
|
||||
s_SeparatorColor = EditorGUIUtility.isProSkin
|
||||
? new Color(0.11f, 0.11f, 0.11f)
|
||||
: new Color(0.5f, 0.5f, 0.5f);
|
||||
|
||||
s_SeparatorStyle = new GUIStyle();
|
||||
s_SeparatorStyle.normal.background = EditorGUIUtility.whiteTexture;
|
||||
s_SeparatorStyle.stretchWidth = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets visible rectangle within GUI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>VisibleRect = TopmostRect + scrollViewOffsets</para>
|
||||
/// </remarks>
|
||||
public static Func<Rect> VisibleRect;
|
||||
|
||||
/// <summary>
|
||||
/// Focus control and text editor where applicable.
|
||||
/// </summary>
|
||||
public static Action<string> FocusTextInControl;
|
||||
|
||||
private static GUIStyle s_TempStyle = new GUIStyle();
|
||||
|
||||
private static GUIContent s_TempIconContent = new GUIContent();
|
||||
private static readonly int s_IconButtonHint = "_ReorderableIconButton_".GetHashCode();
|
||||
|
||||
private static readonly Color s_SeparatorColor;
|
||||
private static readonly GUIStyle s_SeparatorStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Draw texture using <see cref="GUIStyle" /> to workaround bug in Unity where
|
||||
/// <see cref="GUI.DrawTexture" /> flickers when embedded inside a property drawer.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of which to draw texture in space of GUI.</param>
|
||||
/// <param name="texture">Texture.</param>
|
||||
public static void DrawTexture(Rect position, Texture2D texture)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_TempStyle.normal.background = texture;
|
||||
|
||||
s_TempStyle.Draw(position, GUIContent.none, false, false, false, false);
|
||||
}
|
||||
|
||||
public static bool IconButton(Rect position, bool visible, Texture2D iconNormal, Texture2D iconActive, GUIStyle style)
|
||||
{
|
||||
var controlID = GUIUtility.GetControlID(s_IconButtonHint, FocusType.Passive);
|
||||
var result = false;
|
||||
|
||||
position.height += 1;
|
||||
|
||||
switch (Event.current.GetTypeForControl(controlID))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
// Do not allow button to be pressed using right mouse button since
|
||||
// context menu should be shown instead!
|
||||
if (GUI.enabled && Event.current.button != 1 && position.Contains(Event.current.mousePosition))
|
||||
{
|
||||
GUIUtility.hotControl = controlID;
|
||||
GUIUtility.keyboardControl = 0;
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
result = position.Contains(Event.current.mousePosition);
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
if (visible)
|
||||
{
|
||||
var isActive = GUIUtility.hotControl == controlID && position.Contains(Event.current.mousePosition);
|
||||
s_TempIconContent.image = isActive ? iconActive : iconNormal;
|
||||
position.height -= 1;
|
||||
style.Draw(position, s_TempIconContent, isActive, isActive, false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IconButton(Rect position, Texture2D iconNormal, Texture2D iconActive, GUIStyle style)
|
||||
{
|
||||
return IconButton(position, true, iconNormal, iconActive, style);
|
||||
}
|
||||
|
||||
public static void Separator(Rect position, Color color)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
var restoreColor = GUI.color;
|
||||
GUI.color = color;
|
||||
s_SeparatorStyle.Draw(position, false, false, false, false);
|
||||
GUI.color = restoreColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Separator(Rect position)
|
||||
{
|
||||
Separator(position, s_SeparatorColor);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Internal
|
||||
{
|
||||
/// <exclude />
|
||||
public enum ReorderableListTexture
|
||||
{
|
||||
Icon_Add_Normal = 0,
|
||||
Icon_Add_Active,
|
||||
Icon_AddMenu_Normal,
|
||||
Icon_AddMenu_Active,
|
||||
Icon_Menu_Normal,
|
||||
Icon_Menu_Active,
|
||||
Icon_Remove_Normal,
|
||||
Icon_Remove_Active,
|
||||
Button_Normal,
|
||||
Button_Active,
|
||||
Button2_Normal,
|
||||
Button2_Active,
|
||||
TitleBackground,
|
||||
ContainerBackground,
|
||||
Container2Background,
|
||||
GrabHandle,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resources to assist with reorderable list control.
|
||||
/// </summary>
|
||||
/// <exclude />
|
||||
public static class ReorderableListResources
|
||||
{
|
||||
static ReorderableListResources()
|
||||
{
|
||||
GenerateSpecialTextures();
|
||||
LoadResourceAssets();
|
||||
}
|
||||
|
||||
#region Texture Resources
|
||||
|
||||
/// <summary>
|
||||
/// Resource assets for light skin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Resource assets are PNG images which have been encoded using a base-64
|
||||
/// string so that actual asset files are not necessary.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static string[] s_LightSkin =
|
||||
{
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACxJREFUeNpi/P//PwMM6OvrgzkXL15khIkxMRAABBUw6unp/afMBNo7EiDAAEKeD5EsXZcTAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1JREFUeNpi/P//PwMM3L17F8xRVlZmhIkxMRAABBUw3rlz5z9lJtDekQABBgCvqxGbQWpEqwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAICAYAAAD9aA/QAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAERJREFUeNpi/P//PwMxQF9fH6zw4sWLjMSoZ2KgEaCZwYz4ggLmfVwAX7AMjIuJjTxsPqOKi9EtA/GpFhQww2E0QIABAPF5IGHNU7adAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAICAYAAAD9aA/QAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAERJREFUeNpi/P//PwMx4O7du2CFysrKjMSoZ2KgEaCZwYz4ggLmfVwAX7AMjIuJjTxsPqOKi9EtA/GpFhQww2E0QIABACBuGkOOEiPJAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADBJREFUeNpi/P//PwM6YGLAAigUZNHX18ewienixYuMyAJgPshJIKynp/cfxgYIMACCMhb+oVNPwwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADFJREFUeNpi/P//PwM6YGLAAigUZLl79y6GTUzKysqMyAJgPshJIHznzp3/MDZAgAEAkoIW/jHg7H4AAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABVJREFUeNpiVFZWZsAGmBhwAIAAAwAURgBt4C03ZwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAIAAADq9gq6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABVJREFUeNpivHPnDgM2wMSAAwAEGAB8VgKYlvqkBwAAAABJRU5ErkJggg==",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiKCoq+v/p06f/ly9fhmMQHyTOxIAH4JVkARHv379nkJeXhwuC+CDA+P//f4bi4uL/6Lp6e3sZAQIMACmoI7rWhl0KAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiFBER+f/jxw8GNjY2Bhj49esXAwcHBwMTAx6AV5IFRPz58wdFEMZn/P//P4OoqOh/dF2vX79mBAgwADpeFCsbeaC+AAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFBJREFUeNpi/P//P0NxcfF/BjTQ29vLyFBUVPT/4cOH/z99+gTHID5InAWkSlBQkAEoANclLy8PppkY8AC8kmBj379/DzcKxgcBRnyuBQgwACVNLqBePwzmAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAElJREFUeNp8jjEKADEIBNdDrCz1/w+0tRQMOchxpHC6dVhW6m64e+MiIojMrDMTzPyJqoKq4r1sISJ3GQ8GRsln48/JNH27BBgAUhQbSyMxqzEAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpi/P//P0NxcfF/BgRgZP78+fN/VVVVhpCQEAZjY2OGs2fPNrCApBwdHRkePHgAVwoWnDVrFgMyAAgwAAt4E1dCq1obAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADtJREFUeNpi/P//P0NxcfF/Bijo7e1lZCgqKvr/6dOn/5cvXwbTID4TSPb9+/cM8vLyYBoEGLFpBwgwAHGiI8KoD3BZAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAIAAADJUWIXAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpiDA0NZUACLEDc2dkJ4ZSXlzMxoAJGNPUAAQYAwbcFBwYygqkAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACdJREFUeNpi/PTp038GAoClvr6ekBoGxv//CRrEwPL582fqWAcQYAAnaA2zsd+RkQAAAABJRU5ErkJggg==",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Resource assets for dark skin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Resource assets are PNG images which have been encoded using a base-64
|
||||
/// string so that actual asset files are not necessary.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private static string[] s_DarkSkin =
|
||||
{
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE9JREFUeNpi/P//PwM+wITMOXr06H8QxqmAoAnYAOORI0f+U2aCsrIy3ISFCxeC6fj4eIQCZG/CfGBtbc1IvBXIJqioqIA5d+7cgZsAEGAAsHYfVsuw0XYAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAKCAYAAACJxx+AAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEZJREFUeNpi/P//PwM+wITM+Q8FOBUQNAEbYPmPxRHIYoRN4OLignO+ffsGppHFGJFtgBnNCATEW4HMgRn9/ft3uBhAgAEAbZ0gJEmOtOAAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAKCAYAAACwoK7bAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAG1JREFUeNpi/P//PwMtABOxCo8ePfofhKluMM1cTCpgxBfGhLxubW3NOLhcrKKiApdcuHAhmI6Pj4fL37lzhxGXzxiJTW4wzdi8D3IAzGKY5VQJCpDLYT4B0WCfgFxMDFZWVv4PwoTUwNgAAQYA7Mltu4fEN4wAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABYAAAAKCAYAAACwoK7bAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGVJREFUeNpi/P//PwMtABOxCv9DAdUNppmLSQWM+HxHyOuMQEB3F7Pgk+Ti4oKzv337hiH2/ft3nD5jJDaiYZqxeZ+Tk/M/zGKY5VQJCqDLGWE+AdEgPtEuBrkKZgg+NTB5gAADAJGHOCAbby7zAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADpJREFUeNpi/P//PwM6YGLAAmghyHL06FEM65ni4+NRBMB8kDuVlZX/Hzly5D+IBrsbRMAkYGyAAAMAB7YiCOfAQ0cAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAYAAAB8OZQwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADdJREFUeNpi/P//PwM6YGLAAmghyPIfi/VMXFxcKAJgPkghBwfH/3///v0H0WCNIAImAWMDBBgA09Igc2M/ueMAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAYAAACzzX7wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi/P//PwM+wHL06FG8KpgYCABGZWVlvCYABBgA7/sHvGw+cz8AAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAYAAACzzX7wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi/P//PwM+wPKfgAomBgKAhYuLC68CgAADAAxjByOjCHIRAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAERJREFUeNpiVFZW/u/i4sLw4sULBhiQkJBg2LNnDwMTAx6AV5IFRLx9+xZsFAyA+CDA+P//fwYVFZX/6Lru3LnDCBBgAEqlFEYRrf2nAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAFCAYAAACJmvbYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEFJREFUeNpiFBER+f/jxw8GNjY2Bhj49esXAwcHBwMTAx6AV5IFRPz58wdFEMZn/P//P4OoqOh/dF2vX79mBAgwADpeFCsbeaC+AAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAExJREFUeNpi/P//P4OKisp/BjRw584dRhaQhKGhIYOwsDBc4u3bt2ANLCAOSOLFixdwSQkJCTDNxIAH4JVkgdkBMwrGBwFGfK4FCDAAV1AdhemEguIAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAElJREFUeNp8jjEKADEIBNdDrCz1/w+0tRQMOchxpHC6dVhW6m64e+MiIojMrDMTzPyJqoKq4r1sISJ3GQ8GRsln48/JNH27BBgAUhQbSyMxqzEAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADtJREFUeNpi/P//P4OKisp/Bii4c+cOIwtIQE9Pj+HLly9gQRCfBcQACbx69QqmmAEseO/ePQZkABBgAD04FXsmmijSAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAD1JREFUeNpi/P//P4OKisp/Bii4c+cOIwtIwMXFheHFixcMEhISYAVMINm3b9+CBUA0CDCiazc0NGQECDAAdH0YelA27kgAAAAASUVORK5CYII=",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAECAYAAABGM/VAAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACZJREFUeNpi/P//vxQDGmABEffv3/8ME1BUVORlYsACGLFpBwgwABaWCjfQEetnAAAAAElFTkSuQmCC",
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACRJREFUeNpizM3N/c9AADAqKysTVMTi5eXFSFAREFPHOoAAAwBCfwcAO8g48QAAAABJRU5ErkJggg==",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets light or dark version of the specified texture.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static Texture2D GetTexture(ReorderableListTexture name)
|
||||
{
|
||||
return s_Cached[(int)name];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generated Resources
|
||||
|
||||
public static Texture2D texHighlightColor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate special textures.
|
||||
/// </summary>
|
||||
private static void GenerateSpecialTextures()
|
||||
{
|
||||
texHighlightColor = CreatePixelTexture("(Generated) Highlight Color", ReorderableListStyles.SelectionBackgroundColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create 1x1 pixel texture of specified color.
|
||||
/// </summary>
|
||||
/// <param name="name">Name for texture object.</param>
|
||||
/// <param name="color">Pixel color.</param>
|
||||
/// <returns>
|
||||
/// The new <c>Texture2D</c> instance.
|
||||
/// </returns>
|
||||
public static Texture2D CreatePixelTexture(string name, Color color)
|
||||
{
|
||||
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false, LudiqGUIUtility.createLinearTextures);
|
||||
tex.name = name;
|
||||
tex.hideFlags = HideFlags.HideAndDontSave;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.SetPixel(0, 0, color);
|
||||
tex.Apply();
|
||||
return tex;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Load PNG from Base-64 Encoded String
|
||||
|
||||
private static Texture2D[] s_Cached;
|
||||
|
||||
/// <summary>
|
||||
/// Read textures from base-64 encoded strings. Automatically selects assets based
|
||||
/// upon whether the light or dark (pro) skin is active.
|
||||
/// </summary>
|
||||
private static void LoadResourceAssets()
|
||||
{
|
||||
var skin = EditorGUIUtility.isProSkin ? s_DarkSkin : s_LightSkin;
|
||||
s_Cached = new Texture2D[skin.Length];
|
||||
|
||||
for (var i = 0; i < s_Cached.Length; ++i)
|
||||
{
|
||||
// Get image data (PNG) from base64 encoded strings.
|
||||
var imageData = Convert.FromBase64String(skin[i]);
|
||||
|
||||
// Gather image size from image data.
|
||||
int texWidth, texHeight;
|
||||
GetImageSize(imageData, out texWidth, out texHeight);
|
||||
|
||||
// Generate texture asset.
|
||||
var tex = new Texture2D(texWidth, texHeight, TextureFormat.ARGB32, false, LudiqGUIUtility.createLinearTextures);
|
||||
tex.hideFlags = HideFlags.HideAndDontSave;
|
||||
tex.name = "(Generated) ReorderableList:" + i;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.LoadImage(imageData);
|
||||
|
||||
s_Cached[i] = tex;
|
||||
}
|
||||
|
||||
s_LightSkin = null;
|
||||
s_DarkSkin = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read width and height if PNG file in pixels.
|
||||
/// </summary>
|
||||
/// <param name="imageData">PNG image data.</param>
|
||||
/// <param name="width">Width of image in pixels.</param>
|
||||
/// <param name="height">Height of image in pixels.</param>
|
||||
private static void GetImageSize(byte[] imageData, out int width, out int height)
|
||||
{
|
||||
width = ReadInt(imageData, 3 + 15);
|
||||
height = ReadInt(imageData, 3 + 15 + 2 + 2);
|
||||
}
|
||||
|
||||
private static int ReadInt(byte[] imageData, int offset)
|
||||
{
|
||||
return (imageData[offset] << 8) | imageData[offset + 1];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functionality for <see cref="SerializedPropertyAdaptor" /> implementations.
|
||||
/// </summary>
|
||||
public static class SerializedPropertyUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Reset the value of a property.
|
||||
/// </summary>
|
||||
/// <param name="property">Serialized property for a serialized property.</param>
|
||||
public static void ResetValue(SerializedProperty property)
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(property));
|
||||
}
|
||||
|
||||
switch (property.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Integer:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
property.boolValue = false;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
property.floatValue = 0f;
|
||||
break;
|
||||
case SerializedPropertyType.String:
|
||||
property.stringValue = "";
|
||||
break;
|
||||
case SerializedPropertyType.Color:
|
||||
property.colorValue = Color.black;
|
||||
break;
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
property.objectReferenceValue = null;
|
||||
break;
|
||||
case SerializedPropertyType.LayerMask:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
property.enumValueIndex = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
property.vector2Value = default(Vector2);
|
||||
break;
|
||||
case SerializedPropertyType.Vector3:
|
||||
property.vector3Value = default(Vector3);
|
||||
break;
|
||||
case SerializedPropertyType.Vector4:
|
||||
property.vector4Value = default(Vector4);
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
property.rectValue = default(Rect);
|
||||
break;
|
||||
case SerializedPropertyType.ArraySize:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
property.intValue = 0;
|
||||
break;
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
property.animationCurveValue = AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
break;
|
||||
case SerializedPropertyType.Bounds:
|
||||
property.boundsValue = default(Bounds);
|
||||
break;
|
||||
case SerializedPropertyType.Gradient:
|
||||
//!TODO: Amend when Unity add a public API for setting the gradient.
|
||||
break;
|
||||
}
|
||||
|
||||
if (property.isArray)
|
||||
{
|
||||
property.arraySize = 0;
|
||||
}
|
||||
|
||||
ResetChildPropertyValues(property);
|
||||
}
|
||||
|
||||
private static void ResetChildPropertyValues(SerializedProperty element)
|
||||
{
|
||||
if (!element.hasChildren)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var childProperty = element.Copy();
|
||||
var elementPropertyDepth = element.depth;
|
||||
var enterChildren = true;
|
||||
|
||||
while (childProperty.Next(enterChildren) && childProperty.depth > elementPropertyDepth)
|
||||
{
|
||||
enterChildren = false;
|
||||
ResetValue(childProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies value of <paramref name="sourceProperty" /> into <pararef name="destProperty" />.
|
||||
/// </summary>
|
||||
/// <param name="destProperty">Destination property.</param>
|
||||
/// <param name="sourceProperty">Source property.</param>
|
||||
public static void CopyPropertyValue(SerializedProperty destProperty, SerializedProperty sourceProperty)
|
||||
{
|
||||
if (destProperty == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(destProperty));
|
||||
}
|
||||
if (sourceProperty == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceProperty));
|
||||
}
|
||||
|
||||
sourceProperty = sourceProperty.Copy();
|
||||
destProperty = destProperty.Copy();
|
||||
|
||||
CopyPropertyValueSingular(destProperty, sourceProperty);
|
||||
|
||||
if (sourceProperty.hasChildren)
|
||||
{
|
||||
var elementPropertyDepth = sourceProperty.depth;
|
||||
while (sourceProperty.Next(true) && destProperty.Next(true) && sourceProperty.depth > elementPropertyDepth)
|
||||
{
|
||||
CopyPropertyValueSingular(destProperty, sourceProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyPropertyValueSingular(SerializedProperty destProperty, SerializedProperty sourceProperty)
|
||||
{
|
||||
switch (destProperty.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Integer:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Boolean:
|
||||
destProperty.boolValue = sourceProperty.boolValue;
|
||||
break;
|
||||
case SerializedPropertyType.Float:
|
||||
destProperty.floatValue = sourceProperty.floatValue;
|
||||
break;
|
||||
case SerializedPropertyType.String:
|
||||
destProperty.stringValue = sourceProperty.stringValue;
|
||||
break;
|
||||
case SerializedPropertyType.Color:
|
||||
destProperty.colorValue = sourceProperty.colorValue;
|
||||
break;
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
destProperty.objectReferenceValue = sourceProperty.objectReferenceValue;
|
||||
break;
|
||||
case SerializedPropertyType.LayerMask:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Enum:
|
||||
destProperty.enumValueIndex = sourceProperty.enumValueIndex;
|
||||
break;
|
||||
case SerializedPropertyType.Vector2:
|
||||
destProperty.vector2Value = sourceProperty.vector2Value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector3:
|
||||
destProperty.vector3Value = sourceProperty.vector3Value;
|
||||
break;
|
||||
case SerializedPropertyType.Vector4:
|
||||
destProperty.vector4Value = sourceProperty.vector4Value;
|
||||
break;
|
||||
case SerializedPropertyType.Rect:
|
||||
destProperty.rectValue = sourceProperty.rectValue;
|
||||
break;
|
||||
case SerializedPropertyType.ArraySize:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.Character:
|
||||
destProperty.intValue = sourceProperty.intValue;
|
||||
break;
|
||||
case SerializedPropertyType.AnimationCurve:
|
||||
destProperty.animationCurveValue = sourceProperty.animationCurveValue;
|
||||
break;
|
||||
case SerializedPropertyType.Bounds:
|
||||
destProperty.boundsValue = sourceProperty.boundsValue;
|
||||
break;
|
||||
case SerializedPropertyType.Gradient:
|
||||
//!TODO: Amend when Unity add a public API for setting the gradient.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="AddMenuClickedEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class AddMenuClickedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemMovedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="buttonPosition">Position of the add menu button.</param>
|
||||
public AddMenuClickedEventArgs(IReorderableListAdaptor adaptor, Rect buttonPosition)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ButtonPosition = buttonPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets position of the add menu button.
|
||||
/// </summary>
|
||||
public Rect ButtonPosition { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked when the "Add Menu" button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void AddMenuClickedEventHandler(object sender, AddMenuClickedEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemInsertedEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemInsertedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemInsertedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="itemIndex">Zero-based index of item.</param>
|
||||
/// <param name="wasDuplicated">Indicates if inserted item was duplicated from another item.</param>
|
||||
public ItemInsertedEventArgs(IReorderableListAdaptor adaptor, int itemIndex, bool wasDuplicated)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ItemIndex = itemIndex;
|
||||
WasDuplicated = wasDuplicated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets zero-based index of item which was inserted.
|
||||
/// </summary>
|
||||
public int ItemIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if inserted item was duplicated from another item.
|
||||
/// </summary>
|
||||
public bool WasDuplicated { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked after new list item is inserted.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemInsertedEventHandler(object sender, ItemInsertedEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemRemovingEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemRemovingEventArgs : CancelEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemRemovingEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="itemIndex">Zero-based index of item.</param>
|
||||
public ItemRemovingEventArgs(IReorderableListAdaptor adaptor, int itemIndex)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ItemIndex = itemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets zero-based index of item which is being removed.
|
||||
/// </summary>
|
||||
public int ItemIndex { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked before a list item is removed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Item removal can be cancelled by setting <see cref="CancelEventArgs.Cancel" />
|
||||
/// to <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemRemovingEventHandler(object sender, ItemRemovingEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemMovingEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemMovingEventArgs : CancelEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemMovingEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="itemIndex">Zero-based index of item.</param>
|
||||
/// <param name="destinationItemIndex">Xero-based index of item destination.</param>
|
||||
public ItemMovingEventArgs(IReorderableListAdaptor adaptor, int itemIndex, int destinationItemIndex)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
ItemIndex = itemIndex;
|
||||
DestinationItemIndex = destinationItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets current zero-based index of item which is going to be moved.
|
||||
/// </summary>
|
||||
public int ItemIndex { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the new candidate zero-based index for the item.
|
||||
/// </summary>
|
||||
/// <seealso cref="NewItemIndex" />
|
||||
public int DestinationItemIndex { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets zero-based index of item <strong>after</strong> it has been moved.
|
||||
/// </summary>
|
||||
/// <seealso cref="DestinationItemIndex" />
|
||||
public int NewItemIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = DestinationItemIndex;
|
||||
if (result > ItemIndex)
|
||||
{
|
||||
--result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked before a list item is moved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Moving of item can be cancelled by setting <see cref="CancelEventArgs.Cancel" />
|
||||
/// to <c>true</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemMovingEventHandler(object sender, ItemMovingEventArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// Arguments which are passed to <see cref="ItemMovedEventHandler" />.
|
||||
/// </summary>
|
||||
public sealed class ItemMovedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ItemMovedEventArgs" />.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="oldItemIndex">Old zero-based index of item.</param>
|
||||
/// <param name="newItemIndex">New zero-based index of item.</param>
|
||||
public ItemMovedEventArgs(IReorderableListAdaptor adaptor, int oldItemIndex, int newItemIndex)
|
||||
{
|
||||
Adaptor = adaptor;
|
||||
OldItemIndex = oldItemIndex;
|
||||
NewItemIndex = newItemIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets adaptor to reorderable list container which contains element.
|
||||
/// </summary>
|
||||
public IReorderableListAdaptor Adaptor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets old zero-based index of the item which was moved.
|
||||
/// </summary>
|
||||
public int OldItemIndex { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets new zero-based index of the item which was moved.
|
||||
/// </summary>
|
||||
public int NewItemIndex { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An event handler which is invoked after a list item is moved.
|
||||
/// </summary>
|
||||
/// <param name="sender">Object which raised event.</param>
|
||||
/// <param name="args">Event arguments.</param>
|
||||
public delegate void ItemMovedEventHandler(object sender, ItemMovedEventArgs args);
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional flags which can be passed into reorderable list field.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <para>Multiple flags can be specified if desired:</para>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// var flags = ReorderableListFlags.HideAddButton | ReorderableListFlags.HideRemoveButtons;
|
||||
/// ReorderableListGUI.ListField(list, flags);
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
[Flags]
|
||||
public enum ReorderableListFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Hide grab handles and disable reordering of list items.
|
||||
/// </summary>
|
||||
DisableReordering = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Hide add button at base of control.
|
||||
/// </summary>
|
||||
HideAddButton = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Hide remove buttons from list items.
|
||||
/// </summary>
|
||||
HideRemoveButtons = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// Do not display context menu upon right-clicking grab handle.
|
||||
/// </summary>
|
||||
DisableContextMenu = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// Hide "Duplicate" option from context menu.
|
||||
/// </summary>
|
||||
DisableDuplicateCommand = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// Do not automatically focus first control of newly added items.
|
||||
/// </summary>
|
||||
DisableAutoFocus = 0x0020,
|
||||
|
||||
/// <summary>
|
||||
/// Show zero-based index of array elements.
|
||||
/// </summary>
|
||||
ShowIndices = 0x0040,
|
||||
|
||||
/// <exclude />
|
||||
[Obsolete("This flag is redundant because the clipping optimization was removed.")]
|
||||
DisableClipping = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// Do not attempt to automatically scroll when list is inside a scroll view and
|
||||
/// the mouse pointer is dragged outside of the visible portion of the list.
|
||||
/// </summary>
|
||||
DisableAutoScroll = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// Show "Size" field at base of list control.
|
||||
/// </summary>
|
||||
ShowSizeField = 0x0200,
|
||||
}
|
||||
}
|
@@ -0,0 +1,681 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for drawing reorderable lists.
|
||||
/// </summary>
|
||||
public static class ReorderableListGUI
|
||||
{
|
||||
static ReorderableListGUI()
|
||||
{
|
||||
DefaultListControl = new ReorderableListControl();
|
||||
|
||||
// Duplicate default styles to prevent user scripts from interferring with
|
||||
// the default list control instance.
|
||||
DefaultListControl.ContainerStyle = new GUIStyle(ReorderableListStyles.Container);
|
||||
DefaultListControl.FooterButtonStyle = new GUIStyle(ReorderableListStyles.FooterButton);
|
||||
DefaultListControl.ItemButtonStyle = new GUIStyle(ReorderableListStyles.ItemButton);
|
||||
|
||||
IndexOfChangedItem = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default list item height is 18 pixels.
|
||||
/// </summary>
|
||||
public const float DefaultItemHeight = 18;
|
||||
|
||||
private static GUIContent s_Temp = new GUIContent();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the zero-based index of the last item that was changed. A value of -1
|
||||
/// indicates that no item was changed by list.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This property should not be set when items are added or removed.</para>
|
||||
/// </remarks>
|
||||
public static int IndexOfChangedItem { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control ID of the list that is currently being drawn.
|
||||
/// </summary>
|
||||
public static int CurrentListControlID => ReorderableListControl.CurrentListControlID;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the list control that is currently being drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value of this property should be ignored for <see cref="EventType.Layout" />
|
||||
/// type events when using reorderable list controls with automatic layout.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <see cref="CurrentItemTotalPosition" />
|
||||
public static Rect CurrentListPosition => ReorderableListControl.CurrentListPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the zero-based index of the list item that is currently being drawn;
|
||||
/// or a value of -1 if no item is currently being drawn.
|
||||
/// </summary>
|
||||
public static int CurrentItemIndex => ReorderableListControl.CurrentItemIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total position of the list item that is currently being drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The value of this property should be ignored for <see cref="EventType.Layout" />
|
||||
/// type events when using reorderable list controls with automatic layout.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <see cref="CurrentItemIndex" />
|
||||
/// <see cref="CurrentListPosition" />
|
||||
public static Rect CurrentItemTotalPosition => ReorderableListControl.CurrentItemTotalPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default list control implementation.
|
||||
/// </summary>
|
||||
private static ReorderableListControl DefaultListControl { get; set; }
|
||||
|
||||
#region Basic Item Drawers
|
||||
|
||||
/// <summary>
|
||||
/// Default list item drawer implementation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Always presents the label "Item drawer not implemented.".</para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position to draw list item control(s).</param>
|
||||
/// <param name="item">Value of list item.</param>
|
||||
/// <returns>
|
||||
/// Unmodified value of list item.
|
||||
/// </returns>
|
||||
/// <typeparam name="T">Type of list item.</typeparam>
|
||||
public static T DefaultItemDrawer<T>(Rect position, T item)
|
||||
{
|
||||
GUI.Label(position, "Item drawer not implemented.");
|
||||
return item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws text field allowing list items to be edited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Null values are automatically changed to empty strings since null
|
||||
/// values cannot be edited using a text field.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Value of <c>GUI.changed</c> is set to <c>true</c> if value of item
|
||||
/// is modified.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="position">Position to draw list item control(s).</param>
|
||||
/// <param name="item">Value of list item.</param>
|
||||
/// <returns>
|
||||
/// Modified value of list item.
|
||||
/// </returns>
|
||||
public static string TextFieldItemDrawer(Rect position, string item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
item = "";
|
||||
GUI.changed = true;
|
||||
}
|
||||
return EditorGUI.TextField(position, item);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Title Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When needed, should be shown immediately before list field.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// ReorderableListGUI.Title(titleContent);
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// ReorderableListGUI.Title(titleContent);
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
/// <param name="title">Content for title control.</param>
|
||||
public static void Title(GUIContent title)
|
||||
{
|
||||
var position = GUILayoutUtility.GetRect(title, ReorderableListStyles.Title);
|
||||
Title(position, title);
|
||||
LudiqGUI.Space(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When needed, should be shown immediately before list field.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code language="csharp"><![CDATA[
|
||||
/// ReorderableListGUI.Title("Your Title");
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// <code language="unityscript"><![CDATA[
|
||||
/// ReorderableListGUI.Title('Your Title');
|
||||
/// ReorderableListGUI.ListField(list, DynamicListGU.TextFieldItemDrawer);
|
||||
/// ]]></code>
|
||||
/// </example>
|
||||
/// <param name="title">Text for title control.</param>
|
||||
public static void Title(string title)
|
||||
{
|
||||
s_Temp.text = title;
|
||||
Title(s_Temp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field with absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="title">Content for title control.</param>
|
||||
public static void Title(Rect position, GUIContent title)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
ReorderableListStyles.Title.Draw(position, title, false, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw title control for list field with absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="text">Text for title control.</param>
|
||||
public static void Title(Rect position, string text)
|
||||
{
|
||||
s_Temp.text = text;
|
||||
Title(position, s_Temp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List<T> Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control.
|
||||
/// </summary>
|
||||
/// <param name="list">The list which can be reordered.</param>
|
||||
/// <param name="drawItem">Callback to draw list item.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="itemHeight">Height of a single list item.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <typeparam name="T">Type of list item.</typeparam>
|
||||
private static void DoListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new GenericListAdaptor<T>(list, drawItem, itemHeight);
|
||||
ReorderableListControl.DrawControlFromState(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control with absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="list">The list which can be reordered.</param>
|
||||
/// <param name="drawItem">Callback to draw list item.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="itemHeight">Height of a single list item.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <typeparam name="T">Type of list item.</typeparam>
|
||||
private static void DoListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new GenericListAdaptor<T>(list, drawItem, itemHeight);
|
||||
ReorderableListControl.DrawControlFromState(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, float itemHeight)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, float itemHeight)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(list, drawItem, drawEmpty, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, drawEmpty, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, null, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, itemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight)
|
||||
{
|
||||
DoListField(list, drawItem, null, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, float itemHeight)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(list, drawItem, null, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListField{T}(IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmpty, float, ReorderableListFlags)" />
|
||||
public static void ListField<T>(IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem)
|
||||
{
|
||||
DoListField(list, drawItem, null, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute{T}(Rect, IList{T}, ReorderableListControl.ItemDrawer{T}, ReorderableListControl.DrawEmptyAbsolute, float, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute<T>(Rect position, IList<T> list, ReorderableListControl.ItemDrawer<T> drawItem)
|
||||
{
|
||||
DoListFieldAbsolute(position, list, drawItem, null, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate height of list field for absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="itemCount">Count of items in list.</param>
|
||||
/// <param name="itemHeight">Fixed height of list item.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <returns>
|
||||
/// Required list height in pixels.
|
||||
/// </returns>
|
||||
public static float CalculateListFieldHeight(int itemCount, float itemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
// We need to push/pop flags so that nested controls are properly calculated.
|
||||
var restoreFlags = DefaultListControl.Flags;
|
||||
try
|
||||
{
|
||||
DefaultListControl.Flags = flags;
|
||||
return DefaultListControl.CalculateListHeight(itemCount, itemHeight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DefaultListControl.Flags = restoreFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(int, float, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(int itemCount, ReorderableListFlags flags)
|
||||
{
|
||||
return CalculateListFieldHeight(itemCount, DefaultItemHeight, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(int, float, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(int itemCount, float itemHeight)
|
||||
{
|
||||
return CalculateListFieldHeight(itemCount, itemHeight, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(int, float, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(int itemCount)
|
||||
{
|
||||
return CalculateListFieldHeight(itemCount, DefaultItemHeight, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SerializedProperty Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for serializable property array.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serializable property.</param>
|
||||
/// <param name="fixedItemHeight">
|
||||
/// Use fixed height for items rather than
|
||||
/// <see cref="UnityEditor.EditorGUI.GetPropertyHeight(SerializedProperty)" />.
|
||||
/// </param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new SerializedPropertyAdaptor(arrayProperty, fixedItemHeight);
|
||||
ReorderableListControl.DrawControlFromState(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for serializable property array.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="arrayProperty">Serializable property.</param>
|
||||
/// <param name="fixedItemHeight">
|
||||
/// Use fixed height for items rather than
|
||||
/// <see cref="UnityEditor.EditorGUI.GetPropertyHeight(SerializedProperty)" />.
|
||||
/// </param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
var adaptor = new SerializedPropertyAdaptor(arrayProperty, fixedItemHeight);
|
||||
ReorderableListControl.DrawControlFromState(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, 0, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(arrayProperty, 0, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, 0, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty)
|
||||
{
|
||||
DoListField(arrayProperty, 0, null, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, 0, null, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate height of list field for absolute positioning.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serializable property.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <returns>
|
||||
/// Required list height in pixels.
|
||||
/// </returns>
|
||||
public static float CalculateListFieldHeight(SerializedProperty arrayProperty, ReorderableListFlags flags)
|
||||
{
|
||||
// We need to push/pop flags so that nested controls are properly calculated.
|
||||
var restoreFlags = DefaultListControl.Flags;
|
||||
try
|
||||
{
|
||||
DefaultListControl.Flags = flags;
|
||||
return DefaultListControl.CalculateListHeight(new SerializedPropertyAdaptor(arrayProperty));
|
||||
}
|
||||
finally
|
||||
{
|
||||
DefaultListControl.Flags = restoreFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(SerializedProperty, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(SerializedProperty arrayProperty)
|
||||
{
|
||||
return CalculateListFieldHeight(arrayProperty, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SerializedProperty Control (Fixed Item Height)
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, drawEmpty, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(SerializedProperty, float, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(SerializedProperty arrayProperty, float fixedItemHeight)
|
||||
{
|
||||
DoListField(arrayProperty, fixedItemHeight, null, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, SerializedProperty, float, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, SerializedProperty arrayProperty, float fixedItemHeight)
|
||||
{
|
||||
DoListFieldAbsolute(position, arrayProperty, fixedItemHeight, null, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Adaptor Control
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for adapted collection.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListField(IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags = 0)
|
||||
{
|
||||
ReorderableListControl.DrawControlFromState(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw list field control for adapted collection.
|
||||
/// </summary>
|
||||
/// <param name="position">Position of control.</param>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="drawEmpty">Callback to draw custom content for empty list (optional).</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
private static void DoListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags = 0)
|
||||
{
|
||||
ReorderableListControl.DrawControlFromState(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmpty drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmptyAbsolute drawEmpty, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, drawEmpty, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmpty drawEmpty)
|
||||
{
|
||||
DoListField(adaptor, drawEmpty);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListControl.DrawEmptyAbsolute drawEmpty)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, drawEmpty);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor, ReorderableListFlags flags)
|
||||
{
|
||||
DoListField(adaptor, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor, ReorderableListFlags flags)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, null, flags);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoListField(IReorderableListAdaptor, ReorderableListControl.DrawEmpty, ReorderableListFlags)" />
|
||||
public static void ListField(IReorderableListAdaptor adaptor)
|
||||
{
|
||||
DoListField(adaptor, null);
|
||||
}
|
||||
|
||||
/// <inheritdoc
|
||||
/// cref="DoListFieldAbsolute(Rect, IReorderableListAdaptor, ReorderableListControl.DrawEmptyAbsolute, ReorderableListFlags)" />
|
||||
public static void ListFieldAbsolute(Rect position, IReorderableListAdaptor adaptor)
|
||||
{
|
||||
DoListFieldAbsolute(position, adaptor, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate height of list field for adapted collection.
|
||||
/// </summary>
|
||||
/// <param name="adaptor">Reorderable list adaptor.</param>
|
||||
/// <param name="flags">Optional flags to pass into list field.</param>
|
||||
/// <returns>
|
||||
/// Required list height in pixels.
|
||||
/// </returns>
|
||||
public static float CalculateListFieldHeight(IReorderableListAdaptor adaptor, ReorderableListFlags flags)
|
||||
{
|
||||
// We need to push/pop flags so that nested controls are properly calculated.
|
||||
var restoreFlags = DefaultListControl.Flags;
|
||||
try
|
||||
{
|
||||
DefaultListControl.Flags = flags;
|
||||
return DefaultListControl.CalculateListHeight(adaptor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DefaultListControl.Flags = restoreFlags;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CalculateListFieldHeight(IReorderableListAdaptor, ReorderableListFlags)" />
|
||||
public static float CalculateListFieldHeight(IReorderableListAdaptor adaptor)
|
||||
{
|
||||
return CalculateListFieldHeight(adaptor, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using Unity.VisualScripting.ReorderableList.Internal;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Styles for the <see cref="ReorderableListControl" />.
|
||||
/// </summary>
|
||||
public static class ReorderableListStyles
|
||||
{
|
||||
static ReorderableListStyles()
|
||||
{
|
||||
Title = new GUIStyle();
|
||||
Title.border = new RectOffset(2, 2, 2, 1);
|
||||
Title.margin = new RectOffset(5, 5, 5, 0);
|
||||
Title.padding = new RectOffset(5, 5, 3, 3);
|
||||
Title.alignment = TextAnchor.MiddleLeft;
|
||||
Title.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.TitleBackground);
|
||||
Title.normal.textColor = EditorGUIUtility.isProSkin
|
||||
? new Color(0.8f, 0.8f, 0.8f)
|
||||
: new Color(0.2f, 0.2f, 0.2f);
|
||||
|
||||
Container = new GUIStyle();
|
||||
Container.border = new RectOffset(2, 2, 2, 2);
|
||||
Container.margin = new RectOffset(5, 5, 0, 0);
|
||||
Container.padding = new RectOffset(2, 2, 2, 2);
|
||||
Container.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.ContainerBackground);
|
||||
|
||||
Container2 = new GUIStyle(Container);
|
||||
Container2.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.Container2Background);
|
||||
|
||||
FooterButton = new GUIStyle();
|
||||
FooterButton.fixedHeight = 18;
|
||||
FooterButton.alignment = TextAnchor.MiddleCenter;
|
||||
FooterButton.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button_Normal);
|
||||
FooterButton.active.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button_Active);
|
||||
FooterButton.border = new RectOffset(3, 3, 1, 3);
|
||||
FooterButton.padding = new RectOffset(2, 2, 0, 2);
|
||||
FooterButton.clipping = TextClipping.Overflow;
|
||||
|
||||
FooterButton2 = new GUIStyle();
|
||||
FooterButton2.fixedHeight = 18;
|
||||
FooterButton2.alignment = TextAnchor.MiddleCenter;
|
||||
FooterButton2.normal.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button2_Normal);
|
||||
FooterButton2.active.background = ReorderableListResources.GetTexture(ReorderableListTexture.Button2_Active);
|
||||
FooterButton2.border = new RectOffset(3, 3, 3, 3);
|
||||
FooterButton2.padding = new RectOffset(2, 2, 2, 2);
|
||||
FooterButton2.clipping = TextClipping.Overflow;
|
||||
|
||||
ItemButton = new GUIStyle();
|
||||
ItemButton.active.background = ReorderableListResources.CreatePixelTexture("Dark Pixel (List GUI)", new Color32(18, 18, 18, 255));
|
||||
ItemButton.imagePosition = ImagePosition.ImageOnly;
|
||||
ItemButton.alignment = TextAnchor.MiddleCenter;
|
||||
ItemButton.overflow = new RectOffset(0, 0, -1, 0);
|
||||
ItemButton.padding = new RectOffset(0, 0, 1, 0);
|
||||
ItemButton.contentOffset = new Vector2(0, -1f);
|
||||
|
||||
SelectedItem = new GUIStyle();
|
||||
SelectedItem.normal.background = ReorderableListResources.texHighlightColor;
|
||||
SelectedItem.normal.textColor = Color.white;
|
||||
SelectedItem.fontSize = 12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for title header.
|
||||
/// </summary>
|
||||
public static GUIStyle Title { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for the background of list control.
|
||||
/// </summary>
|
||||
public static GUIStyle Container { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternative style for the background of list control.
|
||||
/// </summary>
|
||||
public static GUIStyle Container2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for footer button.
|
||||
/// </summary>
|
||||
public static GUIStyle FooterButton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an alternative style for footer button.
|
||||
/// </summary>
|
||||
public static GUIStyle FooterButton2 { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for remove item button.
|
||||
/// </summary>
|
||||
public static GUIStyle ItemButton { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets style for the background of a selected item.
|
||||
/// </summary>
|
||||
public static GUIStyle SelectedItem { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets color for the horizontal lines that appear between list items.
|
||||
/// </summary>
|
||||
public static Color HorizontalLineColor => EditorGUIUtility.isProSkin ? new Color(1f, 1f, 1f, 0.14f) : new Color(0.59f, 0.59f, 0.59f, 0.55f);
|
||||
|
||||
/// <summary>
|
||||
/// Gets color of background for a selected list item.
|
||||
/// </summary>
|
||||
public static Color SelectionBackgroundColor => EditorGUIUtility.isProSkin ? new Color32(62, 95, 150, 255) : new Color32(62, 125, 231, 255);
|
||||
}
|
||||
}
|
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) Rotorz Limited. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root.
|
||||
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.ReorderableList
|
||||
{
|
||||
/// <summary>
|
||||
/// Reorderable list adaptor for serialized array property.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This adaptor can be subclassed to add special logic to item height calculation.
|
||||
/// You may want to implement a custom adaptor class where specialised functionality
|
||||
/// is needed.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// List elements are <b>not</b> cloned using the <see cref="System.ICloneable" />
|
||||
/// interface when using a <see cref="UnityEditor.SerializedProperty" /> to
|
||||
/// manipulate lists.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class SerializedPropertyAdaptor : IReorderableListAdaptor
|
||||
{
|
||||
private SerializedProperty _arrayProperty;
|
||||
|
||||
/// <summary>
|
||||
/// Fixed height of each list item.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Non-zero value overrides property drawer height calculation
|
||||
/// which is more efficient.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public float FixedItemHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets element from list.
|
||||
/// </summary>
|
||||
/// <param name="index">Zero-based index of element.</param>
|
||||
/// <returns>
|
||||
/// Serialized property wrapper for array element.
|
||||
/// </returns>
|
||||
public SerializedProperty this[int index] => _arrayProperty.GetArrayElementAtIndex(index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying serialized array property.
|
||||
/// </summary>
|
||||
public SerializedProperty arrayProperty => _arrayProperty;
|
||||
|
||||
#region Construction
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SerializedPropertyAdaptor" />.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serialized property for entire array.</param>
|
||||
/// <param name="fixedItemHeight">Non-zero height overrides property drawer height calculation.</param>
|
||||
public SerializedPropertyAdaptor(SerializedProperty arrayProperty, float fixedItemHeight)
|
||||
{
|
||||
if (arrayProperty == null)
|
||||
{
|
||||
throw new ArgumentNullException("Array property was null.");
|
||||
}
|
||||
if (!arrayProperty.isArray)
|
||||
{
|
||||
throw new InvalidOperationException("Specified serialized propery is not an array.");
|
||||
}
|
||||
|
||||
_arrayProperty = arrayProperty;
|
||||
FixedItemHeight = fixedItemHeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="SerializedPropertyAdaptor" />.
|
||||
/// </summary>
|
||||
/// <param name="arrayProperty">Serialized property for entire array.</param>
|
||||
public SerializedPropertyAdaptor(SerializedProperty arrayProperty) : this(arrayProperty, 0f) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region IReorderableListAdaptor - Implementation
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => _arrayProperty.arraySize;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanDrag(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual bool CanRemove(int index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add()
|
||||
{
|
||||
var newIndex = _arrayProperty.arraySize;
|
||||
++_arrayProperty.arraySize;
|
||||
Internal.SerializedPropertyUtility.ResetValue(_arrayProperty.GetArrayElementAtIndex(newIndex));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index)
|
||||
{
|
||||
_arrayProperty.InsertArrayElementAtIndex(index);
|
||||
Internal.SerializedPropertyUtility.ResetValue(_arrayProperty.GetArrayElementAtIndex(index));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Duplicate(int index)
|
||||
{
|
||||
_arrayProperty.InsertArrayElementAtIndex(index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(int index)
|
||||
{
|
||||
// Unity doesn't remove element when it contains an object reference.
|
||||
var elementProperty = _arrayProperty.GetArrayElementAtIndex(index);
|
||||
if (elementProperty.propertyType == SerializedPropertyType.ObjectReference)
|
||||
{
|
||||
elementProperty.objectReferenceValue = null;
|
||||
}
|
||||
|
||||
_arrayProperty.DeleteArrayElementAtIndex(index);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Move(int sourceIndex, int destIndex)
|
||||
{
|
||||
if (destIndex > sourceIndex)
|
||||
{
|
||||
--destIndex;
|
||||
}
|
||||
_arrayProperty.MoveArrayElement(sourceIndex, destIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
_arrayProperty.ClearArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void BeginGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void EndGUI() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItemBackground(Rect position, int index) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DrawItem(Rect position, int index)
|
||||
{
|
||||
EditorGUI.PropertyField(position, this[index], GUIContent.none, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual float GetItemHeight(int index)
|
||||
{
|
||||
return FixedItemHeight != 0f
|
||||
? FixedItemHeight
|
||||
: EditorGUI.GetPropertyHeight(this[index], GUIContent.none, false)
|
||||
;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphDescription : Description, IGraphDescription { }
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(IGraph))]
|
||||
public class GraphDescriptor<TGraph, TGraphDescription> : Descriptor<TGraph, TGraphDescription>
|
||||
where TGraph : class, IGraph
|
||||
where TGraphDescription : class, IGraphDescription, new()
|
||||
{
|
||||
protected GraphDescriptor(TGraph target) : base(target) { }
|
||||
|
||||
protected TGraph graph => target;
|
||||
|
||||
[Assigns(cache = false)]
|
||||
[RequiresUnityAPI]
|
||||
public override string Title()
|
||||
{
|
||||
return StringUtility.FallbackWhitespace(graph.title, graph.GetType().HumanName());
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override string Summary()
|
||||
{
|
||||
return graph.summary;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override EditorTexture Icon()
|
||||
{
|
||||
return graph.GetType().Icon();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class GraphElementDescription : Description, IGraphElementDescription
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphItemDescriptor<TItem, TDescription> : Descriptor<TItem, TDescription>
|
||||
where TItem : class, IGraphItem
|
||||
where TDescription : class, IDescription, new()
|
||||
{
|
||||
protected GraphItemDescriptor(TItem item) : base(item) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class GraphNesterDescriptor
|
||||
{
|
||||
public static string Title(IGraphNester nester)
|
||||
{
|
||||
var graph = nester.childGraph;
|
||||
|
||||
if (!StringUtility.IsNullOrWhiteSpace(graph?.title))
|
||||
{
|
||||
return graph?.title;
|
||||
}
|
||||
|
||||
if (nester.nest.source == GraphSource.Macro && (UnityObject)nester.nest.macro != null)
|
||||
{
|
||||
var macroName = ((UnityObject)nester.nest.macro).name;
|
||||
|
||||
if (BoltCore.Configuration.humanNaming)
|
||||
{
|
||||
return macroName.Prettify();
|
||||
}
|
||||
else
|
||||
{
|
||||
return macroName;
|
||||
}
|
||||
}
|
||||
|
||||
return nester.GetType().HumanName();
|
||||
}
|
||||
|
||||
public static string Summary(IGraphNester nester)
|
||||
{
|
||||
var graph = nester.childGraph;
|
||||
|
||||
if (!StringUtility.IsNullOrWhiteSpace(graph?.summary))
|
||||
{
|
||||
return graph?.summary;
|
||||
}
|
||||
|
||||
return nester.GetType().Summary();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphDescription : IDescription { }
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IGraphElementDescription : IDescription
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IMachineDescription : IDescription { }
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IMacroDescription : IDescription { }
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class MachineDescription : Description, IMachineDescription { }
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(IMachine))]
|
||||
public class MachineDescriptor<TMachine, TMachineDescription> : Descriptor<TMachine, TMachineDescription>
|
||||
where TMachine : UnityObject, IMachine
|
||||
where TMachineDescription : class, IMachineDescription, new()
|
||||
{
|
||||
protected MachineDescriptor(TMachine target) : base(target) { }
|
||||
|
||||
protected TMachine machine => target;
|
||||
|
||||
[Assigns(cache = false)]
|
||||
[RequiresUnityAPI]
|
||||
public override string Title()
|
||||
{
|
||||
return machine.name;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override string Summary()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override EditorTexture Icon()
|
||||
{
|
||||
return machine.GetType().Icon();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class MacroDescription : Description, IMacroDescription { }
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[Descriptor(typeof(IMacro))]
|
||||
public class MacroDescriptor<TMacro, TMacroDescription> : Descriptor<TMacro, TMacroDescription>
|
||||
where TMacro : UnityObject, IMacro
|
||||
where TMacroDescription : class, IMacroDescription, new()
|
||||
{
|
||||
protected MacroDescriptor(TMacro target) : base(target) { }
|
||||
|
||||
protected TMacro macro => target;
|
||||
|
||||
[Assigns(cache = false)]
|
||||
[RequiresUnityAPI]
|
||||
public override string Title()
|
||||
{
|
||||
return macro.name;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override string Summary()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public override EditorTexture Icon()
|
||||
{
|
||||
return macro.GetType().Icon();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class Description : IDescription
|
||||
{
|
||||
public virtual string title { get; set; }
|
||||
public virtual string summary { get; set; }
|
||||
public virtual EditorTexture icon { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Descriptor<TTarget, TDescription> : Assigner<TTarget, TDescription>, IDescriptor
|
||||
where TTarget : class
|
||||
where TDescription : class, IDescription, new()
|
||||
{
|
||||
protected Descriptor(TTarget target) : base(target, new TDescription()) { }
|
||||
|
||||
public override void ValueChanged()
|
||||
{
|
||||
DescriptorProvider.instance.TriggerDescriptionChange(target);
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
public virtual string Title()
|
||||
{
|
||||
return target.ToString();
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
public virtual string Summary()
|
||||
{
|
||||
return target.GetType().Summary();
|
||||
}
|
||||
|
||||
[Assigns]
|
||||
[RequiresUnityAPI]
|
||||
public virtual EditorTexture Icon()
|
||||
{
|
||||
return target.GetType().Icon();
|
||||
}
|
||||
|
||||
object IDescriptor.target => target;
|
||||
|
||||
public TDescription description => assignee;
|
||||
|
||||
IDescription IDescriptor.description => description;
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
|
||||
public sealed class DescriptorAttribute : Attribute, IDecoratorAttribute
|
||||
{
|
||||
public DescriptorAttribute(Type type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type type { get; private set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class DescriptorProvider : SingleDecoratorProvider<object, IDescriptor, DescriptorAttribute>, IDisposable
|
||||
{
|
||||
private readonly Dictionary<object, HashSet<Action>> listeners = new Dictionary<object, HashSet<Action>>();
|
||||
|
||||
protected override bool cache => true;
|
||||
|
||||
private DescriptorProvider()
|
||||
{
|
||||
PluginContainer.delayCall += () => // The provider gets created at runtime on Start for the debug data
|
||||
{
|
||||
BoltCore.Configuration.namingSchemeChanged += DescribeAll;
|
||||
XmlDocumentation.loadComplete += DescribeAll;
|
||||
};
|
||||
}
|
||||
|
||||
public override bool IsValid(object described)
|
||||
{
|
||||
return !described.IsUnityNull();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BoltCore.Configuration.namingSchemeChanged -= DescribeAll;
|
||||
XmlDocumentation.loadComplete -= DescribeAll;
|
||||
ClearListeners();
|
||||
}
|
||||
|
||||
public void AddListener(object describable, Action onDescriptionChange)
|
||||
{
|
||||
if (!listeners.ContainsKey(describable))
|
||||
{
|
||||
listeners.Add(describable, new HashSet<Action>());
|
||||
}
|
||||
|
||||
listeners[describable].Add(onDescriptionChange);
|
||||
}
|
||||
|
||||
public void RemoveListener(object describable, Action onDescriptionChange)
|
||||
{
|
||||
if (!listeners.ContainsKey(describable))
|
||||
{
|
||||
Debug.LogWarning($"Trying to remove unknown description change listener for '{describable}'.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
listeners[describable].Remove(onDescriptionChange);
|
||||
|
||||
if (listeners[describable].Count == 0)
|
||||
{
|
||||
listeners.Remove(describable);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearListeners()
|
||||
{
|
||||
listeners.Clear();
|
||||
}
|
||||
|
||||
public void TriggerDescriptionChange(object describable)
|
||||
{
|
||||
if (!listeners.ContainsKey(describable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var onDescriptionChange in listeners[describable])
|
||||
{
|
||||
onDescriptionChange?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void Describe(object describable)
|
||||
{
|
||||
GetDecorator(describable).isDirty = true;
|
||||
}
|
||||
|
||||
public void DescribeAll()
|
||||
{
|
||||
foreach (var descriptor in decorators.Values)
|
||||
{
|
||||
descriptor.isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public IDescriptor Descriptor(object target)
|
||||
{
|
||||
return GetDecorator(target);
|
||||
}
|
||||
|
||||
public TDescriptor Descriptor<TDescriptor>(object target) where TDescriptor : IDescriptor
|
||||
{
|
||||
return GetDecorator<TDescriptor>(target);
|
||||
}
|
||||
|
||||
public IDescription Description(object target)
|
||||
{
|
||||
var descriptor = Descriptor(target);
|
||||
descriptor.Validate();
|
||||
return descriptor.description;
|
||||
}
|
||||
|
||||
public TDescription Description<TDescription>(object target) where TDescription : IDescription
|
||||
{
|
||||
var description = Description(target);
|
||||
|
||||
if (!(description is TDescription))
|
||||
{
|
||||
throw new InvalidCastException($"Description type mismatch for '{target}': found {description.GetType()}, expected {typeof(TDescription)}.");
|
||||
}
|
||||
|
||||
return (TDescription)description;
|
||||
}
|
||||
|
||||
static DescriptorProvider()
|
||||
{
|
||||
instance = new DescriptorProvider();
|
||||
}
|
||||
|
||||
public static DescriptorProvider instance { get; }
|
||||
}
|
||||
|
||||
public static class XDescriptorProvider
|
||||
{
|
||||
public static void Describe(this object target)
|
||||
{
|
||||
DescriptorProvider.instance.Describe(target);
|
||||
}
|
||||
|
||||
public static bool HasDescriptor(this object target)
|
||||
{
|
||||
Ensure.That(nameof(target)).IsNotNull(target);
|
||||
|
||||
return DescriptorProvider.instance.HasDecorator(target.GetType());
|
||||
}
|
||||
|
||||
public static IDescriptor Descriptor(this object target)
|
||||
{
|
||||
return DescriptorProvider.instance.Descriptor(target);
|
||||
}
|
||||
|
||||
public static TDescriptor Descriptor<TDescriptor>(this object target) where TDescriptor : IDescriptor
|
||||
{
|
||||
return DescriptorProvider.instance.Descriptor<TDescriptor>(target);
|
||||
}
|
||||
|
||||
public static IDescription Description(this object target)
|
||||
{
|
||||
return DescriptorProvider.instance.Description(target);
|
||||
}
|
||||
|
||||
public static TDescription Description<TDescription>(this object target) where TDescription : IDescription
|
||||
{
|
||||
return DescriptorProvider.instance.Description<TDescription>(target);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IDescription
|
||||
{
|
||||
string title { get; }
|
||||
string summary { get; }
|
||||
EditorTexture icon { get; }
|
||||
}
|
||||
|
||||
public static class XDescription
|
||||
{
|
||||
public static GUIContent ToGUIContent(this IDescription description)
|
||||
{
|
||||
return new GUIContent(description.title, null, description.summary);
|
||||
}
|
||||
|
||||
public static GUIContent ToGUIContent(this IDescription description, int iconSize)
|
||||
{
|
||||
return new GUIContent(description.title, description.icon?[iconSize], description.summary);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IDescriptor
|
||||
{
|
||||
object target { get; }
|
||||
|
||||
IDescription description { get; }
|
||||
|
||||
bool isDirty { get; set; }
|
||||
|
||||
void Validate();
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class DocumentationGenerator
|
||||
{
|
||||
public static string GenerateDocumentation(string projectPath)
|
||||
{
|
||||
PathUtility.CreateDirectoryIfNeeded(BoltCore.Paths.assemblyDocumentations);
|
||||
|
||||
var projectName = Path.GetFileNameWithoutExtension(projectPath);
|
||||
|
||||
if (!File.Exists(projectPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Project file not found: '{projectPath}'.");
|
||||
}
|
||||
|
||||
var projectBuilderPath = Paths.projectBuilder;
|
||||
|
||||
if (!File.Exists(projectBuilderPath))
|
||||
{
|
||||
throw new FileNotFoundException($"Project builder not found: '{projectBuilderPath}'.\nYou can download the latest MSBuild from: {Paths.MsBuildDownloadLink}");
|
||||
}
|
||||
|
||||
using (var process = new Process())
|
||||
{
|
||||
var projectXml = XDocument.Load(projectPath);
|
||||
var projectRootNamespace = projectXml.Root.GetDefaultNamespace();
|
||||
var assemblyName = projectXml.Descendants(projectRootNamespace + "AssemblyName").Single().Value;
|
||||
var documentationPath = Path.Combine(BoltCore.Paths.assemblyDocumentations, assemblyName + ".xml");
|
||||
|
||||
process.StartInfo = new ProcessStartInfo();
|
||||
process.StartInfo.FileName = projectBuilderPath;
|
||||
|
||||
process.StartInfo.Arguments =
|
||||
"/p:Configuration=Debug " +
|
||||
"/p:GenerateDocumentation=true " +
|
||||
"/p:WarningLevel=0 " +
|
||||
$"/p:DocumentationFile=\"{documentationPath}\" " +
|
||||
$"\"{projectPath}\"";
|
||||
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
|
||||
var timeout = 20000;
|
||||
|
||||
var output = new StringBuilder();
|
||||
var error = new StringBuilder();
|
||||
|
||||
using (var outputWaitHandle = new AutoResetEvent(false))
|
||||
using (var errorWaitHandle = new AutoResetEvent(false))
|
||||
{
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
outputWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
output.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data == null)
|
||||
{
|
||||
errorWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
error.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
if (process.WaitForExit(timeout) &&
|
||||
outputWaitHandle.WaitOne(timeout) &&
|
||||
errorWaitHandle.WaitOne(timeout))
|
||||
{
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
throw new BuildFailedException($"Failed to build project '{projectName}':\n{process.StartInfo.Arguments}\n{error}\n{output}");
|
||||
}
|
||||
|
||||
XmlDocumentation.ClearCache();
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TimeoutException("Build process timed out.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,378 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[BackgroundWorker]
|
||||
public static class XmlDocumentation
|
||||
{
|
||||
static XmlDocumentation()
|
||||
{
|
||||
documentations = new Dictionary<Assembly, Dictionary<string, XmlDocumentationTags>>();
|
||||
typeDocumentations = new Dictionary<Type, XmlDocumentationTags>();
|
||||
memberDocumentations = new Dictionary<MemberInfo, XmlDocumentationTags>();
|
||||
enumDocumentations = new Dictionary<object, XmlDocumentationTags>();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Assembly, Dictionary<string, XmlDocumentationTags>> documentations;
|
||||
private static readonly Dictionary<Type, XmlDocumentationTags> typeDocumentations;
|
||||
private static readonly Dictionary<MemberInfo, XmlDocumentationTags> memberDocumentations;
|
||||
private static readonly Dictionary<object, XmlDocumentationTags> enumDocumentations;
|
||||
private static readonly object @lock = new object();
|
||||
|
||||
public static event Action loadComplete;
|
||||
|
||||
public static bool loaded { get; private set; }
|
||||
|
||||
private static readonly string[] fallbackDirectories =
|
||||
{
|
||||
BoltCore.Paths.dotNetDocumentation,
|
||||
BoltCore.Paths.assemblyDocumentations
|
||||
};
|
||||
|
||||
public static void BackgroundWork()
|
||||
{
|
||||
var preloadedAssemblies = new List<Assembly>();
|
||||
|
||||
preloadedAssemblies.AddRange(Codebase.settingsAssemblies);
|
||||
preloadedAssemblies.AddRange(Codebase.ludiqEditorAssemblies);
|
||||
|
||||
for (var i = 0; i < preloadedAssemblies.Count; i++)
|
||||
{
|
||||
var assembly = preloadedAssemblies[i];
|
||||
ProgressUtility.DisplayProgressBar($"Documentation ({assembly.GetName().Name})...", null, (float)i / Codebase.settingsAssemblies.Count);
|
||||
var documentation = GetDocumentationUncached(assembly);
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
if (!documentations.ContainsKey(assembly))
|
||||
{
|
||||
documentations.Add(assembly, documentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnityAPI.Async(() =>
|
||||
{
|
||||
loaded = true;
|
||||
loadComplete?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
documentations.Clear();
|
||||
typeDocumentations.Clear();
|
||||
memberDocumentations.Clear();
|
||||
enumDocumentations.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, XmlDocumentationTags> Documentation(Assembly assembly)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!documentations.ContainsKey(assembly))
|
||||
{
|
||||
documentations.Add(assembly, GetDocumentationUncached(assembly));
|
||||
}
|
||||
|
||||
return documentations[assembly];
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, XmlDocumentationTags> GetDocumentationUncached(Assembly assembly)
|
||||
{
|
||||
var assemblyPath = assembly.Location;
|
||||
|
||||
var documentationPath = Path.ChangeExtension(assemblyPath, ".xml");
|
||||
|
||||
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
documentationPath = "/" + documentationPath;
|
||||
#endif
|
||||
|
||||
if (!File.Exists(documentationPath))
|
||||
{
|
||||
foreach (var fallbackDirectory in fallbackDirectories)
|
||||
{
|
||||
if (Directory.Exists(fallbackDirectory))
|
||||
{
|
||||
var fallbackPath = Path.Combine(fallbackDirectory, Path.GetFileName(documentationPath));
|
||||
|
||||
if (File.Exists(fallbackPath))
|
||||
{
|
||||
documentationPath = fallbackPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(documentationPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
XDocument document;
|
||||
|
||||
try
|
||||
{
|
||||
document = XDocument.Load(documentationPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("Failed to load XML documentation:\n" + ex);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var ns = document.Root.Name.Namespace;
|
||||
|
||||
var dictionary = new Dictionary<string, XmlDocumentationTags>();
|
||||
|
||||
foreach (var memberElement in document.Element(ns + "doc").Element(ns + "members").Elements(ns + "member"))
|
||||
{
|
||||
var nameAttribute = memberElement.Attribute("name");
|
||||
|
||||
if (nameAttribute != null)
|
||||
{
|
||||
if (dictionary.ContainsKey(nameAttribute.Value))
|
||||
{
|
||||
// Unity sometimes has duplicate member documentation in their XMLs.
|
||||
// Safely skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
dictionary.Add(nameAttribute.Value, new XmlDocumentationTags(memberElement));
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
public static XmlDocumentationTags Documentation(this MemberInfo memberInfo)
|
||||
{
|
||||
if (memberInfo is Type)
|
||||
{
|
||||
return ((Type)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is MethodInfo)
|
||||
{
|
||||
return ((MethodInfo)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is FieldInfo)
|
||||
{
|
||||
return ((FieldInfo)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is PropertyInfo)
|
||||
{
|
||||
return ((PropertyInfo)memberInfo).Documentation();
|
||||
}
|
||||
else if (memberInfo is ConstructorInfo)
|
||||
{
|
||||
return ((ConstructorInfo)memberInfo).Documentation();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ParameterSummary(this MethodBase methodBase, ParameterInfo parameterInfo)
|
||||
{
|
||||
return methodBase.Documentation()?.ParameterSummary(parameterInfo);
|
||||
}
|
||||
|
||||
public static string Summary(this MemberInfo memberInfo)
|
||||
{
|
||||
return memberInfo.Documentation()?.summary;
|
||||
}
|
||||
|
||||
public static string Summary(this Enum @enum)
|
||||
{
|
||||
return @enum.Documentation()?.summary;
|
||||
}
|
||||
|
||||
public static XmlDocumentationTags Documentation(this Enum @enum)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!enumDocumentations.ContainsKey(@enum))
|
||||
{
|
||||
enumDocumentations.Add(@enum, GetDocumentationFromNameInherited(@enum.GetType(), 'F', @enum.ToString(), null));
|
||||
}
|
||||
|
||||
return enumDocumentations[@enum];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this MethodInfo methodInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(methodInfo))
|
||||
{
|
||||
var methodDocumentation = GetDocumentationFromNameInherited(methodInfo.DeclaringType, 'M', methodInfo.Name, methodInfo.GetParameters());
|
||||
|
||||
methodDocumentation?.CompleteWithMethodBase(methodInfo, methodInfo.ReturnType);
|
||||
|
||||
memberDocumentations.Add(methodInfo, methodDocumentation);
|
||||
}
|
||||
|
||||
return memberDocumentations[methodInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this FieldInfo fieldInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(fieldInfo))
|
||||
{
|
||||
memberDocumentations.Add(fieldInfo, GetDocumentationFromNameInherited(fieldInfo.DeclaringType, 'F', fieldInfo.Name, null));
|
||||
}
|
||||
|
||||
return memberDocumentations[fieldInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this PropertyInfo propertyInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(propertyInfo))
|
||||
{
|
||||
memberDocumentations.Add(propertyInfo, GetDocumentationFromNameInherited(propertyInfo.DeclaringType, 'P', propertyInfo.Name, null));
|
||||
}
|
||||
|
||||
return memberDocumentations[propertyInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this ConstructorInfo constructorInfo)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!memberDocumentations.ContainsKey(constructorInfo))
|
||||
{
|
||||
var constructorDocumentation = GetDocumentationFromNameInherited(constructorInfo.DeclaringType, 'M', "#ctor", constructorInfo.GetParameters());
|
||||
|
||||
constructorDocumentation?.CompleteWithMethodBase(constructorInfo, constructorInfo.DeclaringType);
|
||||
|
||||
memberDocumentations.Add(constructorInfo, constructorDocumentation);
|
||||
}
|
||||
|
||||
return memberDocumentations[constructorInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags Documentation(this Type type)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (!loaded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!typeDocumentations.ContainsKey(type))
|
||||
{
|
||||
typeDocumentations.Add(type, GetDocumentationFromNameInherited(type, 'T', null, null));
|
||||
}
|
||||
|
||||
return typeDocumentations[type];
|
||||
}
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags GetDocumentationFromNameInherited(Type type, char prefix, string memberName, IEnumerable<ParameterInfo> parameterTypes)
|
||||
{
|
||||
var documentation = GetDocumentationFromName(type, prefix, memberName, parameterTypes);
|
||||
|
||||
if (documentation != null && documentation.inherit)
|
||||
{
|
||||
foreach (var implementedType in type.BaseTypeAndInterfaces())
|
||||
{
|
||||
var implementedDocumentation = GetDocumentationFromNameInherited(implementedType, prefix, memberName, parameterTypes);
|
||||
|
||||
if (implementedDocumentation != null)
|
||||
{
|
||||
return implementedDocumentation;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return documentation;
|
||||
}
|
||||
|
||||
private static XmlDocumentationTags GetDocumentationFromName(Type type, char prefix, string memberName, IEnumerable<ParameterInfo> parameterTypes)
|
||||
{
|
||||
var documentation = Documentation(type.Assembly);
|
||||
|
||||
if (documentation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
type = type.GetGenericTypeDefinition();
|
||||
}
|
||||
|
||||
var fullName = $"{prefix}:{type.Namespace}{(type.Namespace != null ? "." : "")}{type.Name.Replace('+', '.')}";
|
||||
|
||||
if (!string.IsNullOrEmpty(memberName))
|
||||
{
|
||||
fullName += "." + memberName;
|
||||
|
||||
if (parameterTypes != null && parameterTypes.Any())
|
||||
{
|
||||
fullName += "(" + string.Join(",", parameterTypes.Select(p => p.ParameterType.ToString() + (p.IsOut || p.ParameterType.IsByRef ? "@" : "")).ToArray()) + ")";
|
||||
}
|
||||
}
|
||||
|
||||
if (documentation.ContainsKey(fullName))
|
||||
{
|
||||
return documentation[fullName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class XmlDocumentationTags
|
||||
{
|
||||
public XmlDocumentationTags()
|
||||
{
|
||||
parameterTypes = new Dictionary<string, Type>();
|
||||
parameters = new Dictionary<string, string>();
|
||||
typeParameters = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public XmlDocumentationTags(string summary) : this()
|
||||
{
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public XmlDocumentationTags(XElement xml) : this()
|
||||
{
|
||||
foreach (var childNode in xml.Elements())
|
||||
{
|
||||
if (childNode.Name.LocalName == "summary")
|
||||
{
|
||||
summary = ProcessText(childNode.Value);
|
||||
}
|
||||
else if (childNode.Name.LocalName == "returns")
|
||||
{
|
||||
returns = ProcessText(childNode.Value);
|
||||
}
|
||||
else if (childNode.Name.LocalName == "remarks")
|
||||
{
|
||||
remarks = ProcessText(childNode.Value);
|
||||
}
|
||||
else if (childNode.Name.LocalName == "param")
|
||||
{
|
||||
var paramText = ProcessText(childNode.Value);
|
||||
|
||||
var nameAttribute = childNode.Attribute("name");
|
||||
|
||||
if (paramText != null && nameAttribute != null)
|
||||
{
|
||||
if (parameters.ContainsKey(nameAttribute.Value))
|
||||
{
|
||||
parameters[nameAttribute.Value] = paramText;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add(nameAttribute.Value, paramText);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childNode.Name.LocalName == "typeparam")
|
||||
{
|
||||
var typeParamText = ProcessText(childNode.Value);
|
||||
|
||||
var nameAttribute = childNode.Attribute("name");
|
||||
|
||||
if (typeParamText != null && nameAttribute != null)
|
||||
{
|
||||
if (typeParameters.ContainsKey(nameAttribute.Value))
|
||||
{
|
||||
typeParameters[nameAttribute.Value] = typeParamText;
|
||||
}
|
||||
else
|
||||
{
|
||||
typeParameters.Add(nameAttribute.Value, typeParamText);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (childNode.Name.LocalName == "inheritdoc")
|
||||
{
|
||||
inherit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool methodBaseCompleted = true;
|
||||
|
||||
public string summary { get; set; }
|
||||
public string returns { get; set; }
|
||||
public string remarks { get; set; }
|
||||
public Dictionary<string, string> parameters { get; private set; }
|
||||
public Dictionary<string, string> typeParameters { get; private set; }
|
||||
public Dictionary<string, Type> parameterTypes { get; private set; }
|
||||
public Type returnType { get; set; }
|
||||
public bool inherit { get; private set; }
|
||||
|
||||
public void CompleteWithMethodBase(MethodBase methodBase, Type returnType)
|
||||
{
|
||||
if (methodBaseCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parameterInfos = methodBase.GetParameters();
|
||||
|
||||
foreach (var parameterInfo in parameterInfos)
|
||||
{
|
||||
parameterTypes.Add(parameterInfo.Name, parameterInfo.ParameterType);
|
||||
}
|
||||
|
||||
// Remove parameter summaries if no matching parameter is found.
|
||||
// (Happens frequently in Unity methods)
|
||||
foreach (var parameter in parameters.ToArray())
|
||||
{
|
||||
if (parameterInfos.All(p => p.Name != parameter.Key))
|
||||
{
|
||||
parameters.Remove(parameter.Key);
|
||||
}
|
||||
}
|
||||
|
||||
methodBaseCompleted = true;
|
||||
}
|
||||
|
||||
public string ParameterSummary(ParameterInfo parameter)
|
||||
{
|
||||
if (parameters.ContainsKey(parameter.Name))
|
||||
{
|
||||
return parameters[parameter.Name];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ProcessText(string xmlText)
|
||||
{
|
||||
xmlText = string.Join(" ", xmlText.Trim().Split(new[] { ' ', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
if (xmlText == string.Empty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return xmlText;
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 989 B |
After Width: | Height: | Size: 945 B |
After Width: | Height: | Size: 935 B |
After Width: | Height: | Size: 965 B |
@@ -0,0 +1,48 @@
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
"Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with one or more other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
|
||||
"Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
|
||||
"Licensor" means the individual, individuals, entity or entities that offers the Work under the terms of this License.
|
||||
"Original Author" means the individual, individuals, entity or entities who created the Work.
|
||||
"Work" means the copyrightable work of authorship offered under the terms of this License.
|
||||
"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
|
||||
2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
|
||||
|
||||
to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
|
||||
to create and reproduce Derivative Works provided that any such Derivative Work, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";;
|
||||
to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
|
||||
to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
|
||||
For the avoidance of doubt, where the Work is a musical composition:
|
||||
|
||||
Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or, in the event that Licensor is a member of a performance rights society (e.g. ASCAP, BMI, SESAC), via that society, royalties for the public performance or public digital performance (e.g. webcast) of the Work.
|
||||
Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights agency or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
|
||||
Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).
|
||||
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.
|
||||
|
||||
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
|
||||
|
||||
You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of a recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. When You distribute, publicly display, publicly perform, or publicly digitally perform the Work, You may not impose any technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by Section 4(b), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by Section 4(b), as requested.
|
||||
If You distribute, publicly display, publicly perform, or publicly digitally perform the Work (as defined in Section 1 above) or any Derivative Works (as defined in Section 1 above) or Collective Works (as defined in Section 1 above), You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, consistent with Section 3(b) in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(b) may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear, if a credit for all contributing authors of the Derivative Work or Collective Work appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND ONLY TO THE EXTENT OF ANY RIGHTS HELD IN THE LICENSED WORK BY THE LICENSOR. THE LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MARKETABILITY, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works (as defined in Section 1 above) or Collective Works (as defined in Section 1 above) from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
|
||||
Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
|
||||
8. Miscellaneous
|
||||
|
||||
Each time You distribute or publicly digitally perform the Work (as defined in Section 1 above) or a Collective Work (as defined in Section 1 above), the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
|
||||
Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
|
||||
If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
|
||||
No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
|
||||
This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
|
After Width: | Height: | Size: 181 B |
After Width: | Height: | Size: 367 B |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 365 B |
After Width: | Height: | Size: 763 B |