first commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class EditorSplineGizmos
|
||||
{
|
||||
public static bool showSelectedGizmo = false;
|
||||
|
||||
[DrawGizmo(GizmoType.Active | GizmoType.NonSelected | GizmoType.Selected | GizmoType.Pickable)]
|
||||
// ReSharper disable once Unity.ParameterNotDerivedFromComponent
|
||||
static void DrawUnselectedSplineGizmos(ISplineContainer provider, GizmoType gizmoType)
|
||||
{
|
||||
//Skip if tool engaged is a spline tool
|
||||
if (typeof(SplineTool).IsAssignableFrom(ToolManager.activeToolType) && !showSelectedGizmo && (gizmoType & GizmoType.Selected) > 0)
|
||||
return;
|
||||
|
||||
var prev = Gizmos.color;
|
||||
Gizmos.color = (gizmoType & (GizmoType.Selected | GizmoType.Active)) > 0
|
||||
? Handles.selectedColor
|
||||
: SplineGizmoUtility.s_GizmosLineColor.value;
|
||||
SplineGizmoUtility.DrawGizmos(provider);
|
||||
Gizmos.color = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0357b2fb04a410d8dc62740f4fe4cbb
|
||||
timeCreated: 1618421082
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eaa3cd6309cba3244b2a227f3e8e7eb3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class BezierTangentPropertyField<T> : DropdownField
|
||||
where T : ISelectableElement
|
||||
{
|
||||
static readonly SplineGUIUtility.EqualityComparer<T> s_Comparer = (a, b) =>
|
||||
EditorSplineUtility.GetKnot(a).Mode == EditorSplineUtility.GetKnot(b).Mode;
|
||||
|
||||
static readonly List<string> k_Modes = new List<string>{L10n.Tr("Mirrored"), L10n.Tr("Continuous"), L10n.Tr("Broken")};
|
||||
|
||||
static readonly string k_Tooltip = L10n.Tr(
|
||||
"Mirrored Tangents:\nIf Knot or InTangent is selected, OutTangent will be mirrored on InTangent. Else, InTangent will be mirrored on OutTangent.\n" +
|
||||
"Continuous Tangents:\nInTangent and OutTangent are always aligned.\n" +
|
||||
"Broken Tangents:\nInTangent and OutTangent are dissociated.\n"
|
||||
);
|
||||
|
||||
IReadOnlyList<T> m_Elements = new List<T>(0);
|
||||
|
||||
public event Action changed;
|
||||
|
||||
internal BezierTangentPropertyField() : base(k_Modes, 0)
|
||||
{
|
||||
label = L10n.Tr("Bezier");
|
||||
name = "ModesDropdown";
|
||||
tooltip = k_Tooltip;
|
||||
|
||||
this.RegisterValueChangedCallback(OnValueChange);
|
||||
}
|
||||
|
||||
bool ShouldShow(IReadOnlyList<T> targets)
|
||||
{
|
||||
// Don't show if an element in the selection isn't a bezier mode
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
var knot = EditorSplineUtility.GetKnot(targets[i]);
|
||||
if (!SplineUtility.AreTangentsModifiable(knot.Mode))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static TangentMode GetTangentModeFromIndex(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return TangentMode.Mirrored;
|
||||
case 1: return TangentMode.Continuous;
|
||||
case 2: return TangentMode.Broken;
|
||||
default: return TangentMode.Mirrored;
|
||||
}
|
||||
}
|
||||
|
||||
static int GetIndexFromTangentMode(TangentMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case TangentMode.Mirrored: return 0;
|
||||
case TangentMode.Continuous: return 1;
|
||||
case TangentMode.Broken: return 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(IReadOnlyList<T> targets)
|
||||
{
|
||||
SetEnabled(ShouldShow(targets));
|
||||
|
||||
m_Elements = targets;
|
||||
SetValueWithoutNotify(EditorSplineUtility.GetKnot(targets[0]).Mode);
|
||||
|
||||
showMixedValue = SplineGUIUtility.HasMultipleValues(targets, s_Comparer);
|
||||
}
|
||||
|
||||
public void SetValueWithoutNotify(TangentMode mode)
|
||||
{
|
||||
SetValueWithoutNotify(k_Modes[GetIndexFromTangentMode(mode)]);
|
||||
}
|
||||
|
||||
void OnValueChange(ChangeEvent<string> evt)
|
||||
{
|
||||
var index = k_Modes.IndexOf(evt.newValue);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
showMixedValue = false;
|
||||
var targetMode = GetTangentModeFromIndex(index);
|
||||
|
||||
EditorSplineUtility.RecordSelection(SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
for (int i = 0; i < m_Elements.Count; ++i)
|
||||
{
|
||||
var knot = EditorSplineUtility.GetKnot(m_Elements[i]);
|
||||
if (m_Elements[i] is SelectableTangent tangent)
|
||||
knot.SetTangentMode(targetMode, (BezierTangent)tangent.TangentIndex);
|
||||
else
|
||||
knot.Mode = targetMode;
|
||||
}
|
||||
|
||||
changed?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84ba2b41d2954a2d8adc64619daa77e8
|
||||
timeCreated: 1649959836
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class CommonElementDrawer : ElementDrawer<ISelectableElement>
|
||||
{
|
||||
readonly TangentModePropertyField<ISelectableElement> m_Mode;
|
||||
readonly BezierTangentPropertyField<ISelectableElement> m_BezierMode;
|
||||
|
||||
public CommonElementDrawer()
|
||||
{
|
||||
Add(m_Mode = new TangentModePropertyField<ISelectableElement>());
|
||||
m_Mode.changed += () => { m_BezierMode.Update(targets); };
|
||||
|
||||
Add(m_BezierMode = new BezierTangentPropertyField<ISelectableElement>());
|
||||
m_BezierMode.changed += () => { m_Mode.Update(targets); };
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
m_Mode.Update(targets);
|
||||
m_BezierMode.Update(targets);
|
||||
}
|
||||
|
||||
public override string GetLabelForTargets()
|
||||
{
|
||||
int knotCount = 0;
|
||||
int tangentCount = 0;
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
if (targets[i] is SelectableKnot)
|
||||
++knotCount;
|
||||
else if (targets[i] is SelectableTangent)
|
||||
++tangentCount;
|
||||
}
|
||||
|
||||
return $"<b>({knotCount}) Knots</b>, <b>({tangentCount}) Tangents</b> selected";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0dd2407e33307e4386616f5c33463fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
interface IElementDrawer
|
||||
{
|
||||
bool HasKnot(Spline spline, int index);
|
||||
void PopulateTargets(IReadOnlyList<SplineInfo> splines);
|
||||
void Update();
|
||||
string GetLabelForTargets();
|
||||
}
|
||||
|
||||
abstract class ElementDrawer<T> : VisualElement, IElementDrawer where T : ISelectableElement
|
||||
{
|
||||
public List<T> targets { get; } = new List<T>();
|
||||
public T target => targets[0];
|
||||
|
||||
public virtual void Update() {}
|
||||
public virtual string GetLabelForTargets() => string.Empty;
|
||||
|
||||
public bool HasKnot(Spline spline, int index)
|
||||
{
|
||||
foreach (var t in targets)
|
||||
if (t.SplineInfo.Spline == spline && t.KnotIndex == index)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void PopulateTargets(IReadOnlyList<SplineInfo> splines)
|
||||
{
|
||||
SplineSelection.GetElements(splines, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95245c7aa2899544b8fe0daa4947e0be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using UnityEngine.Splines;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class ElementInspector : VisualElement, IDisposable
|
||||
{
|
||||
public static bool ignoreKnotCallbacks = false;
|
||||
|
||||
static readonly string k_NoSelectionMessage = L10n.Tr("No element selected");
|
||||
|
||||
readonly Label m_KnotIdentifierLabel;
|
||||
readonly Label m_ErrorMessage;
|
||||
readonly SplineActionButtons m_SplineActionButtons;
|
||||
readonly VisualElement m_SplineDrawerRoot;
|
||||
|
||||
IElementDrawer m_ElementDrawer;
|
||||
readonly CommonElementDrawer m_CommonElementDrawer = new CommonElementDrawer();
|
||||
readonly BezierKnotDrawer m_BezierKnotDrawer = new BezierKnotDrawer();
|
||||
readonly TangentDrawer m_TangentDrawer = new TangentDrawer();
|
||||
|
||||
static StyleSheet s_CommonStyleSheet;
|
||||
static StyleSheet s_ThemeStyleSheet;
|
||||
|
||||
public ElementInspector()
|
||||
{
|
||||
if (s_CommonStyleSheet == null)
|
||||
s_CommonStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Packages/com.unity.splines/Editor/Stylesheets/SplineInspectorCommon.uss");
|
||||
if (s_ThemeStyleSheet == null)
|
||||
s_ThemeStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"Packages/com.unity.splines/Editor/Stylesheets/SplineInspector{(EditorGUIUtility.isProSkin ? "Dark" : "Light")}.uss");
|
||||
|
||||
styleSheets.Add(s_CommonStyleSheet);
|
||||
styleSheets.Add(s_ThemeStyleSheet);
|
||||
|
||||
Add(m_KnotIdentifierLabel = new Label());
|
||||
m_KnotIdentifierLabel.style.height = 24;
|
||||
m_KnotIdentifierLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
|
||||
Add(m_ErrorMessage = new Label { name = "ErrorMessage"});
|
||||
Add(m_SplineDrawerRoot = new VisualElement());
|
||||
Add(m_SplineActionButtons = new SplineActionButtons());
|
||||
|
||||
Spline.Changed += OnKnotModified;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Spline.Changed -= OnKnotModified;
|
||||
}
|
||||
|
||||
void OnKnotModified(Spline spline, int index, SplineModification modification)
|
||||
{
|
||||
if (modification == SplineModification.KnotModified && !ignoreKnotCallbacks && m_ElementDrawer != null && m_ElementDrawer.HasKnot(spline, index))
|
||||
m_ElementDrawer.Update();
|
||||
}
|
||||
|
||||
public void UpdateSelection(IReadOnlyList<SplineInfo> selectedSplines)
|
||||
{
|
||||
UpdateDrawerForElements(selectedSplines);
|
||||
|
||||
if (SplineSelection.Count < 1 || m_ElementDrawer == null)
|
||||
{
|
||||
ShowErrorMessage(k_NoSelectionMessage);
|
||||
m_KnotIdentifierLabel.style.display = DisplayStyle.None;
|
||||
m_SplineActionButtons.style.display = DisplayStyle.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
HideErrorMessage();
|
||||
m_ElementDrawer.PopulateTargets(selectedSplines);
|
||||
m_ElementDrawer.Update();
|
||||
m_KnotIdentifierLabel.text = m_ElementDrawer.GetLabelForTargets();
|
||||
m_KnotIdentifierLabel.style.display = DisplayStyle.Flex;
|
||||
m_SplineActionButtons.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
m_SplineActionButtons.RefreshSelection(selectedSplines);
|
||||
}
|
||||
|
||||
void UpdateDrawerForElements(IReadOnlyList<SplineInfo> selectedSplines)
|
||||
{
|
||||
bool hasKnot = SplineSelection.HasAny<SelectableKnot>(selectedSplines);
|
||||
bool hasTangent = SplineSelection.HasAny<SelectableTangent>(selectedSplines);
|
||||
|
||||
IElementDrawer targetDrawer;
|
||||
if (hasKnot && hasTangent)
|
||||
targetDrawer = m_CommonElementDrawer;
|
||||
else if (hasKnot)
|
||||
targetDrawer = m_BezierKnotDrawer;
|
||||
else if (hasTangent)
|
||||
targetDrawer = m_TangentDrawer;
|
||||
else
|
||||
targetDrawer = null;
|
||||
|
||||
if (targetDrawer == m_ElementDrawer)
|
||||
return;
|
||||
|
||||
m_SplineDrawerRoot.Clear();
|
||||
if (targetDrawer != null)
|
||||
m_SplineDrawerRoot.Add((VisualElement) targetDrawer);
|
||||
|
||||
m_ElementDrawer = targetDrawer;
|
||||
}
|
||||
|
||||
void ShowErrorMessage(string error)
|
||||
{
|
||||
m_ErrorMessage.style.display = DisplayStyle.Flex;
|
||||
m_ErrorMessage.text = error;
|
||||
}
|
||||
|
||||
void HideErrorMessage()
|
||||
{
|
||||
m_ErrorMessage.style.display = DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5d8a953f9ef52441bd58f4c1e5853a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a property drawer for <see cref="EmbeddedSplineData"/> types.
|
||||
/// </summary>
|
||||
/// <seealso cref="EmbeddedSplineDataFieldsAttribute"/>
|
||||
[CustomPropertyDrawer(typeof(EmbeddedSplineData))]
|
||||
[CustomPropertyDrawer(typeof(EmbeddedSplineDataFieldsAttribute))]
|
||||
public class EmbeddedSplineDataPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
bool m_AttemptedFindSplineContainer;
|
||||
static readonly string k_SplineDataKeyContent = "Key";
|
||||
|
||||
static Rect ReserveLine(ref Rect rect, int lines = 1)
|
||||
{
|
||||
var ret = SplineGUIUtility.ReserveSpace(EditorGUIUtility.singleLineHeight * lines, ref rect);
|
||||
rect.y += EditorGUIUtility.standardVerticalSpacing * lines;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int GetSetBitCount(EmbeddedSplineDataField fields)
|
||||
{
|
||||
int c = 0, e = (int) fields;
|
||||
for(int i = 0; i < 4; ++i)
|
||||
if ((e & (1 << i)) != 0)
|
||||
++c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height of a SerializedProperty in pixels.
|
||||
/// </summary>
|
||||
/// <param name="property">The SerializedProperty to calculate height for.</param>
|
||||
/// <param name="label">The label of the SerializedProperty.</param>
|
||||
/// <returns>The height of a SerializedProperty in pixels.</returns>
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var flags = attribute is EmbeddedSplineDataFieldsAttribute attrib
|
||||
? attrib.Fields
|
||||
: EmbeddedSplineDataField.All;
|
||||
|
||||
if (!property.isExpanded)
|
||||
return EditorGUIUtility.singleLineHeight;
|
||||
|
||||
// reserve one line for foldout
|
||||
float height = EditorGUIUtility.singleLineHeight;
|
||||
height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * GetSetBitCount(flags);
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an interface for a SerializedProperty with an <see cref="EmbeddedSplineData"/> type.
|
||||
/// </summary>
|
||||
/// <param name="position">Rectangle on the screen to use for the property GUI.</param>
|
||||
/// <param name="property">The SerializedProperty to make the custom GUI for.</param>
|
||||
/// <param name="label">The label of this property.</param>
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var flags = attribute is EmbeddedSplineDataFieldsAttribute filter
|
||||
? filter.Fields
|
||||
: EmbeddedSplineDataField.All;
|
||||
var fields = GetSetBitCount(flags);
|
||||
|
||||
var container = property.FindPropertyRelative("m_Container");
|
||||
var index = property.FindPropertyRelative("m_SplineIndex");
|
||||
var key = property.FindPropertyRelative("m_Key");
|
||||
var type = property.FindPropertyRelative("m_Type");
|
||||
|
||||
if (fields > 1)
|
||||
{
|
||||
property.isExpanded = EditorGUI.Foldout(ReserveLine(ref position),
|
||||
property.isExpanded,
|
||||
label?.text ?? property.displayName);
|
||||
|
||||
if (!property.isExpanded)
|
||||
return;
|
||||
}
|
||||
|
||||
label = fields == 1 ? label : null;
|
||||
|
||||
// don't create key in property editor
|
||||
// don't copy key values to empty targets
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// only attempt to reconcile the SplineContainer value once per lifetime of drawer. otherwise you get some
|
||||
// odd behaviour when trying to delete or replace the value in the inspector.
|
||||
if (!m_AttemptedFindSplineContainer
|
||||
&& container.objectReferenceValue == null
|
||||
&& property.serializedObject.targetObject is Component cmp
|
||||
&& cmp.TryGetComponent<SplineContainer>(out var spcnt))
|
||||
{
|
||||
container.objectReferenceValue = spcnt;
|
||||
GUI.changed = true;
|
||||
}
|
||||
|
||||
m_AttemptedFindSplineContainer = true;
|
||||
|
||||
if((flags & EmbeddedSplineDataField.Container) == EmbeddedSplineDataField.Container)
|
||||
EditorGUI.PropertyField(ReserveLine(ref position), container, label);
|
||||
|
||||
if (!(container?.objectReferenceValue is SplineContainer component))
|
||||
component = null;
|
||||
|
||||
if ((flags & EmbeddedSplineDataField.SplineIndex) == EmbeddedSplineDataField.SplineIndex)
|
||||
SplineGUI.SplineIndexField(ReserveLine(ref position), index, label, component);
|
||||
|
||||
if ((flags & EmbeddedSplineDataField.Key) == EmbeddedSplineDataField.Key)
|
||||
{
|
||||
string[] keys = component == null || index.intValue < 0 || index.intValue >= component.Splines.Count
|
||||
? Array.Empty<string>()
|
||||
: component[index.intValue].GetSplineDataKeys((EmbeddedSplineDataType) type.enumValueIndex).ToArray();
|
||||
var i = Array.IndexOf(keys, key.stringValue);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
i = EditorGUI.Popup(ReserveLine(ref position), label?.text ?? k_SplineDataKeyContent, i, keys);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
key.stringValue = keys[i];
|
||||
}
|
||||
|
||||
if((flags & EmbeddedSplineDataField.Type) == EmbeddedSplineDataField.Type)
|
||||
EditorGUI.PropertyField(ReserveLine(ref position), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44f1bb87db78405e95edea0e310287b2
|
||||
timeCreated: 1671225065
|
||||
@@ -0,0 +1,206 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(TangentMode))]
|
||||
class TangentModePropertyDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly GUIContent[] k_TangentModeLabels = new[]
|
||||
{
|
||||
new GUIContent("Auto", "The path from this knot to the preceding and following knots is a bezier curve that is automatically calculated from the neighboring knot positions."),
|
||||
new GUIContent("Linear", "The path from this knot to the preceding and following knots is a straight line."),
|
||||
new GUIContent("Bezier", "The path from this knot to the preceding and following knots is a bezier curve with manually defined tangents."),
|
||||
};
|
||||
|
||||
static readonly string[] k_BezierModeLabels = new[]
|
||||
{
|
||||
"Mirrored",
|
||||
"Continuous",
|
||||
"Broken"
|
||||
};
|
||||
|
||||
public static float GetPropertyHeight() => SplineGUIUtility.lineHeight * 2 + 2;
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => GetPropertyHeight();
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
int mode = property.enumValueIndex;
|
||||
int type = System.Math.Min(mode, (int) TangentMode.Mirrored);
|
||||
int bezier = System.Math.Max(mode, (int) TangentMode.Mirrored) - (int)TangentMode.Mirrored;
|
||||
bool mixed = EditorGUI.showMixedValue;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
|
||||
type = GUI.Toolbar(EditorGUI.IndentedRect(SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref position)),
|
||||
type,
|
||||
k_TangentModeLabels);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
// Continuous mode should be prefered instead of Mirrored when switching from AutoSmooth to Bezier
|
||||
// as Centripetal Catmull-Rom's tangents are not always of equal length.
|
||||
if (property.enumValueIndex == (int)TangentMode.AutoSmooth && type == (int)TangentMode.Mirrored)
|
||||
type = (int)TangentMode.Continuous;
|
||||
|
||||
property.enumValueIndex = type;
|
||||
}
|
||||
|
||||
position.y += 2;
|
||||
position.height = SplineGUIUtility.lineHeight;
|
||||
|
||||
EditorGUI.BeginDisabledGroup(mode < (int) TangentMode.Mirrored);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
|
||||
bezier = EditorGUI.Popup(position, "Bezier", bezier, k_BezierModeLabels);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
property.enumValueIndex = bezier + (int)TangentMode.Mirrored;
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUI.showMixedValue = mixed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specialized UI for drawing a knot property drawer with additional data (ex, TangentMode from Spline.MetaData).
|
||||
/// Additionally supports inline fields a little longer than the regular inspector wide mode would allow.
|
||||
/// </summary>
|
||||
static class KnotPropertyDrawerUI
|
||||
{
|
||||
static readonly GUIContent k_Position = EditorGUIUtility.TrTextContent("Position");
|
||||
static readonly GUIContent k_Rotation = EditorGUIUtility.TrTextContent("Rotation");
|
||||
static readonly GUIContent k_TangentLengthContent = EditorGUIUtility.TrTextContent("Tangent Length");
|
||||
static readonly GUIContent k_TangentLengthContentIn = EditorGUIUtility.TrTextContent("Tangent In Length");
|
||||
static readonly GUIContent k_TangentLengthContentOut = EditorGUIUtility.TrTextContent("Tangent Out Length");
|
||||
|
||||
const float k_IndentPad = 13f; // kIndentPerLevel - margin (probably)
|
||||
const int k_MinWideModeWidth = 230;
|
||||
const int k_WideModeInputFieldWidth = 212;
|
||||
|
||||
static bool CanForceWideMode() => EditorGUIUtility.currentViewWidth > k_MinWideModeWidth;
|
||||
|
||||
public static float GetPropertyHeight(SerializedProperty knot, SerializedProperty meta, GUIContent _)
|
||||
{
|
||||
// title
|
||||
float height = SplineGUIUtility.lineHeight;
|
||||
// position, rotation
|
||||
height += SplineGUIUtility.lineHeight * (CanForceWideMode() ? 2 : 4);
|
||||
// 1. { linear, auto, bezier }
|
||||
// 2. { broken, continuous, mirrored }
|
||||
height += meta == null ? 0 : TangentModePropertyDrawer.GetPropertyHeight();
|
||||
// 3. (optional) tangent in
|
||||
// 4. (optional) tangent out
|
||||
height += TangentGetPropertyHeight(meta);
|
||||
|
||||
return knot.isExpanded ? height : SplineGUIUtility.lineHeight;
|
||||
}
|
||||
|
||||
public static float TangentGetPropertyHeight(SerializedProperty meta)
|
||||
{
|
||||
var prop = meta?.FindPropertyRelative("Mode");
|
||||
var mode = meta == null ? TangentMode.Broken : (TangentMode) prop.enumValueIndex;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case TangentMode.AutoSmooth:
|
||||
case TangentMode.Linear:
|
||||
return 0;
|
||||
|
||||
case TangentMode.Mirrored:
|
||||
return SplineGUIUtility.lineHeight;
|
||||
|
||||
case TangentMode.Continuous:
|
||||
return SplineGUIUtility.lineHeight * 2;
|
||||
|
||||
case TangentMode.Broken:
|
||||
default:
|
||||
return SplineGUIUtility.lineHeight * (CanForceWideMode() ? 2 : 4);
|
||||
}
|
||||
}
|
||||
|
||||
public static void TangentOnGUI(ref Rect rect,
|
||||
SerializedProperty tangentIn,
|
||||
SerializedProperty tangentOut,
|
||||
TangentMode mode)
|
||||
{
|
||||
if (mode == TangentMode.Broken)
|
||||
{
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpaceForLine(ref rect), tangentIn);
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpaceForLine(ref rect), tangentOut);
|
||||
return;
|
||||
}
|
||||
|
||||
// tangents are not serialized as vec3, they are a generic type
|
||||
var tin = tangentIn.FindPropertyRelative("z");
|
||||
var tout = tangentOut.FindPropertyRelative("z");
|
||||
|
||||
if (mode == TangentMode.Mirrored)
|
||||
{
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref rect), tout, k_TangentLengthContent);
|
||||
tin.floatValue = -tout.floatValue;
|
||||
}
|
||||
else if (mode == TangentMode.Continuous)
|
||||
{
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref rect), tin, k_TangentLengthContentIn);
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref rect), tout, k_TangentLengthContentOut);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OnGUI(Rect rect, SerializedProperty knot, SerializedProperty meta, GUIContent label)
|
||||
{
|
||||
bool wideMode = EditorGUIUtility.wideMode;
|
||||
|
||||
if (!wideMode && CanForceWideMode())
|
||||
{
|
||||
EditorGUIUtility.wideMode = true;
|
||||
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - k_WideModeInputFieldWidth;
|
||||
}
|
||||
else
|
||||
EditorGUIUtility.labelWidth = 0;
|
||||
|
||||
var titleRect = SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref rect);
|
||||
titleRect.width = EditorGUIUtility.labelWidth;
|
||||
knot.isExpanded = EditorGUI.Foldout(titleRect, knot.isExpanded, label);
|
||||
var position = knot.FindPropertyRelative("Position");
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (knot.isExpanded)
|
||||
{
|
||||
var mode = meta?.FindPropertyRelative("Mode");
|
||||
bool modeAllowsRotationAndTangent = mode?.enumValueIndex > (int) TangentMode.Linear;
|
||||
var tangentMode = mode != null ? (TangentMode) mode.enumValueIndex : TangentMode.Broken;
|
||||
|
||||
var rotation = knot.FindPropertyRelative("Rotation");
|
||||
var tangentIn = knot.FindPropertyRelative("TangentIn");
|
||||
var tangentOut = knot.FindPropertyRelative("TangentOut");
|
||||
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpaceForLine(ref rect), position, k_Position);
|
||||
EditorGUI.BeginDisabledGroup(!modeAllowsRotationAndTangent);
|
||||
SplineGUILayout.QuaternionField(SplineGUIUtility.ReserveSpaceForLine(ref rect), k_Rotation, rotation);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (meta != null)
|
||||
{
|
||||
EditorGUI.PropertyField(rect, mode);
|
||||
rect.y += TangentModePropertyDrawer.GetPropertyHeight();
|
||||
}
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!modeAllowsRotationAndTangent);
|
||||
TangentOnGUI(ref rect, tangentIn, tangentOut, tangentMode);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
// When in wide mode, show the position field inline with the knot title if not expanded
|
||||
else if (EditorGUIUtility.wideMode)
|
||||
{
|
||||
var inlinePositionRect = titleRect;
|
||||
inlinePositionRect.x += titleRect.width - k_IndentPad * EditorGUI.indentLevel;
|
||||
inlinePositionRect.width = rect.width - (titleRect.width - k_IndentPad * EditorGUI.indentLevel);
|
||||
EditorGUI.PropertyField(inlinePositionRect, position, GUIContent.none);
|
||||
}
|
||||
|
||||
EditorGUIUtility.wideMode = wideMode;
|
||||
EditorGUIUtility.labelWidth = 0;
|
||||
return EditorGUI.EndChangeCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3e99075e258cf04898636e5e72ec3e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,304 @@
|
||||
using UnityEditorInternal;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
class KnotReorderableList : ReorderableList
|
||||
{
|
||||
ISplineContainer m_Container;
|
||||
int m_ContainerIndex;
|
||||
Spline m_Spline;
|
||||
SerializedProperty m_KnotElement, m_MetaElement;
|
||||
SerializedObject serializedObject => serializedProperty.serializedObject;
|
||||
|
||||
static Dictionary<int, KnotReorderableList> s_Pool = new();
|
||||
|
||||
static KnotReorderableList()
|
||||
{
|
||||
Selection.selectionChanged += ClearPool;
|
||||
SplineSelection.changed += SyncKnotSelection;
|
||||
}
|
||||
|
||||
static int GetPropertyHash(SerializedProperty prop)
|
||||
{
|
||||
if(prop.serializedObject.targetObject == null)
|
||||
return 0;
|
||||
|
||||
unchecked
|
||||
{
|
||||
int hash = prop.serializedObject.targetObject.GetInstanceID();
|
||||
hash = hash * 31 * prop.propertyPath.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
static void ClearPool() => s_Pool.Clear();
|
||||
|
||||
static void SyncKnotSelection()
|
||||
{
|
||||
foreach (var kvp in s_Pool)
|
||||
kvp.Value.SyncSelection();
|
||||
|
||||
// InspectorWindow is private for some reason.
|
||||
foreach (var win in Resources.FindObjectsOfTypeAll<EditorWindow>())
|
||||
if (win.GetType().Name.Contains("Inspector"))
|
||||
win.Repaint();
|
||||
}
|
||||
|
||||
public static KnotReorderableList Get(SerializedProperty splineProperty)
|
||||
{
|
||||
int hash = GetPropertyHash(splineProperty);
|
||||
if (!s_Pool.TryGetValue(hash, out var list))
|
||||
s_Pool.Add(hash, list = new KnotReorderableList(splineProperty));
|
||||
list.Init(splineProperty);
|
||||
return list;
|
||||
}
|
||||
|
||||
public KnotReorderableList(SerializedProperty splineProperty)
|
||||
: base(splineProperty.serializedObject, splineProperty.FindPropertyRelative("m_Knots"), true, false, true, true)
|
||||
{
|
||||
Init(splineProperty);
|
||||
multiSelect = true;
|
||||
elementHeightCallback = GetElementHeight;
|
||||
drawElementCallback = OnDrawElement;
|
||||
onReorderCallbackWithDetails = OnReorder;
|
||||
onSelectCallback = OnSelect;
|
||||
onAddCallback = OnAdd;
|
||||
onRemoveCallback = OnRemove;
|
||||
SyncSelection();
|
||||
}
|
||||
|
||||
void Init(SerializedProperty splineProperty)
|
||||
{
|
||||
m_KnotElement = splineProperty.FindPropertyRelative("m_Knots");
|
||||
serializedProperty = m_KnotElement;
|
||||
m_MetaElement = splineProperty.FindPropertyRelative("m_MetaData");
|
||||
|
||||
// only set the ISplineContainer if we are able to determine the correct index into the splines array
|
||||
if (serializedObject.targetObject is ISplineContainer container)
|
||||
{
|
||||
// make sure that this correctly handles the case where ISplineContainer does not serialize an array.
|
||||
// in these cases we'll assert that there is only one spline. if that isn't the case we can't reasonably
|
||||
// handle it so just screw it and give up; the UI can accomodate the case where no container is present.
|
||||
if (SerializedPropertyUtility.TryGetSplineIndex(splineProperty, out m_ContainerIndex))
|
||||
{
|
||||
m_Container = container;
|
||||
|
||||
if (container.Splines.Count > 0)
|
||||
{
|
||||
if (m_Container.Splines.Count < m_ContainerIndex)
|
||||
m_ContainerIndex = m_Container.Splines.Count - 1;
|
||||
|
||||
m_Spline = m_Container.Splines[m_ContainerIndex];
|
||||
}
|
||||
}
|
||||
else if (container.Splines.Count == 1)
|
||||
{
|
||||
m_Container = container;
|
||||
m_Spline = m_Container.Splines[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SerializedProperty GetMetaPropertySafe(int i)
|
||||
{
|
||||
while(m_MetaElement.arraySize < m_KnotElement.arraySize)
|
||||
m_MetaElement.InsertArrayElementAtIndex(m_MetaElement.arraySize);
|
||||
return m_MetaElement.GetArrayElementAtIndex(i);
|
||||
}
|
||||
|
||||
float GetElementHeight(int i) => KnotPropertyDrawerUI.GetPropertyHeight(
|
||||
m_KnotElement.GetArrayElementAtIndex(i),
|
||||
GetMetaPropertySafe(i), GUIContent.none);
|
||||
|
||||
void OnDrawElement(Rect position, int i, bool isActive, bool isFocused)
|
||||
{
|
||||
var knot = m_KnotElement.GetArrayElementAtIndex(i);
|
||||
var meta = GetMetaPropertySafe(i);
|
||||
|
||||
bool guiChanged = false;
|
||||
|
||||
// For reasons unknown, a nested reorderable list requires indent to be incremented to draw elements
|
||||
// with the correct offset. As a hack, we do so when a container is present since we know it will be nested
|
||||
// within a spline reorderable list.
|
||||
if (m_Container != null)
|
||||
{
|
||||
++EditorGUI.indentLevel;
|
||||
guiChanged = KnotPropertyDrawerUI.OnGUI(position, knot, meta, new GUIContent($"Knot [{i}]"));
|
||||
--EditorGUI.indentLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
guiChanged = KnotPropertyDrawerUI.OnGUI(position, knot, meta, new GUIContent($"Knot [{i}]"));
|
||||
}
|
||||
|
||||
if (guiChanged && m_Spline != null)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
m_Spline.EnforceTangentModeNoNotify(m_Spline.PreviousIndex(i));
|
||||
m_Spline.EnforceTangentModeNoNotify(i);
|
||||
m_Spline.EnforceTangentModeNoNotify(m_Spline.NextIndex(i));
|
||||
m_Spline.SetDirty(SplineModification.KnotModified, i);
|
||||
|
||||
// delay repaint because SplineCacheUtility is only clearing it's cache on Spline.afterSplineWasModified
|
||||
EditorApplication.delayCall += SceneView.RepaintAll;
|
||||
}
|
||||
}
|
||||
|
||||
static void EnforceTangentModeWithNeighbors(Spline spline, int index)
|
||||
{
|
||||
if (spline == null)
|
||||
return;
|
||||
int p = spline.PreviousIndex(index), n = spline.NextIndex(index);
|
||||
spline.EnforceTangentModeNoNotify(index);
|
||||
if(p != index) spline.EnforceTangentModeNoNotify(p);
|
||||
if(n != index) spline.EnforceTangentModeNoNotify(n);
|
||||
}
|
||||
|
||||
void OnReorder(ReorderableList reorderableList, int srcIndex, int dstIndex)
|
||||
{
|
||||
m_MetaElement.MoveArrayElement(srcIndex, dstIndex);
|
||||
|
||||
if (m_Container != null)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
m_Container.KnotLinkCollection.KnotIndexChanged(m_ContainerIndex, srcIndex, dstIndex);
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
EnforceTangentModeWithNeighbors(m_Spline, srcIndex);
|
||||
EnforceTangentModeWithNeighbors(m_Spline, dstIndex);
|
||||
m_Spline?.SetDirty(SplineModification.KnotReordered, dstIndex);
|
||||
}
|
||||
|
||||
void OnSelect(ReorderableList reorderableList)
|
||||
{
|
||||
if (m_Container != null)
|
||||
{
|
||||
|
||||
SplineSelection.ClearInspectorSelectedSplines();
|
||||
|
||||
var selected = selectedIndices.Select(x => new SelectableSplineElement(
|
||||
new SelectableKnot(new SplineInfo(m_Container, m_ContainerIndex), x))).ToList();
|
||||
|
||||
var evt = Event.current;
|
||||
|
||||
// At the time of this callback, selectedIndices is already set with the correct selection accounting
|
||||
// for shift, ctrl and command. If any modifiers where present, we need to _not_ clear the selection
|
||||
// on other splines but completely replace the selection on this spline.
|
||||
if (evt.modifiers == EventModifiers.Command
|
||||
|| evt.modifiers == EventModifiers.Shift
|
||||
|| evt.modifiers == EventModifiers.Control)
|
||||
{
|
||||
selected.AddRange(SplineSelection.selection.Where(x =>
|
||||
!ReferenceEquals(x.target, m_Container) || x.targetIndex != m_ContainerIndex));
|
||||
}
|
||||
|
||||
SplineSelection.Set(selected);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
void OnAdd(ReorderableList _)
|
||||
{
|
||||
if (m_Container != null)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
var selectedIndex = index;
|
||||
var info = new SplineInfo(m_Container, m_ContainerIndex);
|
||||
EditorSplineUtility.RecordObject(info, "Add Knot");
|
||||
|
||||
if (selectedIndex < count - 1)
|
||||
{
|
||||
var knot = EditorSplineUtility.InsertKnot(info, selectedIndex + 1, 0.5f);
|
||||
|
||||
SplineSelection.Set(knot);
|
||||
|
||||
index = selectedIndex + 1;
|
||||
Select(selectedIndex + 1);
|
||||
}
|
||||
else // last element from the list
|
||||
{
|
||||
var knot = new SelectableKnot(info, selectedIndex);
|
||||
if (knot.IsValid())
|
||||
{
|
||||
EditorSplineUtility.AddKnotToTheEnd(
|
||||
info,
|
||||
knot.Position + 3f * knot.TangentOut.Direction,
|
||||
math.rotate(knot.LocalToWorld, math.up()),
|
||||
knot.TangentOut.Direction);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorSplineUtility.AddKnotToTheEnd(
|
||||
info,
|
||||
info.Transform.position,
|
||||
math.up(),
|
||||
math.forward());
|
||||
}
|
||||
|
||||
index = count;
|
||||
Select(count);
|
||||
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
}
|
||||
else // if the Spline is not in a ISplineContainer, make default reorderable list
|
||||
{
|
||||
defaultBehaviours.DoAddButton(this);
|
||||
m_MetaElement.InsertArrayElementAtIndex(m_MetaElement.arraySize);
|
||||
}
|
||||
}
|
||||
|
||||
void OnRemove(ReorderableList _)
|
||||
{
|
||||
var toRemove = new List<int>(selectedIndices);
|
||||
toRemove.Sort();
|
||||
|
||||
// Mimic behaviour of a list inspector - if nothing's explicitly selected, remove active or last knot.
|
||||
if (toRemove.Count == 0 && m_Spline.Count > 0)
|
||||
toRemove.Add(index);
|
||||
|
||||
if (m_Container != null)
|
||||
{
|
||||
var info = new SplineInfo(m_Container, m_ContainerIndex);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
Undo.RecordObject(serializedObject.targetObject, "Remove Knot");
|
||||
|
||||
for (int i = toRemove.Count - 1; i >= 0; --i)
|
||||
EditorSplineUtility.RemoveKnot(new SelectableKnot(info, toRemove[i]));
|
||||
|
||||
SplineSelection.Clear();
|
||||
ClearSelection();
|
||||
|
||||
serializedObject.Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultBehaviours.DoRemoveButton(this);
|
||||
|
||||
for (int i = toRemove.Count - 1; i >= 0; --i)
|
||||
m_MetaElement.DeleteArrayElementAtIndex(toRemove[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncSelection()
|
||||
{
|
||||
ClearSelection();
|
||||
|
||||
if (m_Container == null)
|
||||
return;
|
||||
|
||||
foreach (var i in SplineSelection.selection
|
||||
.Where(x => ReferenceEquals(x.target, m_Container) && x.targetIndex == m_ContainerIndex && x.tangentIndex < 0)
|
||||
.Select(y => y.knotIndex))
|
||||
Select(i, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30dcd166c0e84ff5bf290b88ce72bdda
|
||||
timeCreated: 1669232635
|
||||
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class ReadOnlyField : BaseField<string>
|
||||
{
|
||||
readonly Label m_IndexField;
|
||||
|
||||
public ReadOnlyField(string label) : base(label, new Label() { name = "ReadOnlyValue" })
|
||||
{
|
||||
style.flexDirection = FlexDirection.Row;
|
||||
|
||||
m_IndexField = this.Q<Label>("ReadOnlyValue");
|
||||
m_IndexField.text = value;
|
||||
m_IndexField.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||
}
|
||||
|
||||
public override void SetValueWithoutNotify(string newValue)
|
||||
{
|
||||
m_IndexField.text = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37aa8727811c9e04785d9c0e4cafc5d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class SplineActionButtons : VisualElement
|
||||
{
|
||||
static readonly string k_LinkButtonLabel = L10n.Tr("Link");
|
||||
static readonly string k_UnlinkButtonLabel = L10n.Tr("Unlink");
|
||||
static readonly string k_SplitButtonLabel = L10n.Tr("Split");
|
||||
static readonly string k_JoinButtonLabel = L10n.Tr("Join");
|
||||
static readonly string k_ReverseFlowButtonLabel = L10n.Tr("Reverse Spline Flow");
|
||||
|
||||
static readonly string k_LinkButtonTooltip = L10n.Tr("Set knots to the same position and link them.");
|
||||
static readonly string k_UnlinkButtonTooltip = L10n.Tr("Unlink knots.");
|
||||
static readonly string k_SplitButtonTooltip = L10n.Tr("Divide a knot with two segments into two knots.");
|
||||
static readonly string k_JoinButtonTooltip = L10n.Tr("Connect the ends of two splines to each other.");
|
||||
static readonly string k_ReverseFlowButtonTooltip = L10n.Tr("Reverse the direction of a spline.");
|
||||
|
||||
static readonly List<SelectableKnot> m_KnotBuffer = new List<SelectableKnot>();
|
||||
|
||||
IReadOnlyList<SplineInfo> m_SelectedSplines = new List<SplineInfo>();
|
||||
|
||||
readonly Button m_LinkButton;
|
||||
readonly Button m_UnlinkButton;
|
||||
readonly Button m_SplitButton;
|
||||
readonly Button m_JoinButton;
|
||||
readonly Button m_ReverseFlowButton;
|
||||
|
||||
public SplineActionButtons()
|
||||
{
|
||||
#if !UNITY_2023_2_OR_NEWER
|
||||
Add(new Separator());
|
||||
|
||||
style.flexDirection = FlexDirection.Column;
|
||||
|
||||
var firstRow = new VisualElement();
|
||||
Add(firstRow);
|
||||
firstRow.AddToClassList("button-strip");
|
||||
firstRow.style.flexDirection = FlexDirection.Row;
|
||||
|
||||
m_LinkButton = new Button();
|
||||
m_LinkButton.text = k_LinkButtonLabel;
|
||||
m_LinkButton.tooltip = k_LinkButtonTooltip;
|
||||
m_LinkButton.style.flexGrow = new StyleFloat(1);
|
||||
m_LinkButton.clicked += OnLinkClicked;
|
||||
m_LinkButton.AddToClassList("button-strip-button");
|
||||
m_LinkButton.AddToClassList("button-strip-button--left");
|
||||
firstRow.Add(m_LinkButton);
|
||||
|
||||
m_UnlinkButton = new Button();
|
||||
m_UnlinkButton.text = k_UnlinkButtonLabel;
|
||||
m_UnlinkButton.tooltip = k_UnlinkButtonTooltip;
|
||||
m_UnlinkButton.style.flexGrow = new StyleFloat(1);
|
||||
m_UnlinkButton.clicked += OnUnlinkClicked;
|
||||
m_UnlinkButton.AddToClassList("button-strip-button");
|
||||
m_UnlinkButton.AddToClassList("button-strip-button--right");
|
||||
m_UnlinkButton.AddToClassList("button-strip-button--right-spacer");
|
||||
firstRow.Add(m_UnlinkButton);
|
||||
|
||||
m_SplitButton = new Button();
|
||||
m_SplitButton.text = k_SplitButtonLabel;
|
||||
m_SplitButton.tooltip = k_SplitButtonTooltip;
|
||||
m_SplitButton.style.flexGrow = 1;
|
||||
m_SplitButton.clicked += OnSplitClicked;
|
||||
m_SplitButton.AddToClassList("button-strip-button");
|
||||
m_SplitButton.AddToClassList("button-strip-button--left");
|
||||
m_SplitButton.AddToClassList("button-strip-button--left-spacer");
|
||||
firstRow.Add(m_SplitButton);
|
||||
|
||||
m_JoinButton = new Button();
|
||||
m_JoinButton.text = k_JoinButtonLabel;
|
||||
m_JoinButton.tooltip = k_JoinButtonTooltip;
|
||||
m_JoinButton.style.flexGrow = 1;
|
||||
m_JoinButton.clicked += OnJoinClicked;
|
||||
m_JoinButton.AddToClassList("button-strip-button");
|
||||
m_JoinButton.AddToClassList("button-strip-button--right");
|
||||
firstRow.Add(m_JoinButton);
|
||||
|
||||
m_ReverseFlowButton = new Button();
|
||||
m_ReverseFlowButton.text = k_ReverseFlowButtonLabel;
|
||||
m_ReverseFlowButton.tooltip = k_ReverseFlowButtonTooltip;
|
||||
m_ReverseFlowButton.style.flexGrow = 1;
|
||||
m_ReverseFlowButton.clicked += OnReverseFlowClicked;
|
||||
Add(m_ReverseFlowButton);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnLinkClicked()
|
||||
{
|
||||
SplineSelection.GetElements(m_SelectedSplines, m_KnotBuffer);
|
||||
EditorSplineUtility.LinkKnots(m_KnotBuffer);
|
||||
RefreshSelection(m_SelectedSplines);
|
||||
}
|
||||
|
||||
void OnUnlinkClicked()
|
||||
{
|
||||
SplineSelection.GetElements(m_SelectedSplines, m_KnotBuffer);
|
||||
EditorSplineUtility.UnlinkKnots(m_KnotBuffer);
|
||||
RefreshSelection(m_SelectedSplines);
|
||||
}
|
||||
|
||||
void OnSplitClicked()
|
||||
{
|
||||
EditorSplineUtility.RecordSelection("Split knot");
|
||||
SplineSelection.Set(EditorSplineUtility.SplitKnot(m_KnotBuffer[0]));
|
||||
}
|
||||
|
||||
void OnJoinClicked()
|
||||
{
|
||||
EditorSplineUtility.RecordSelection("Join knot");
|
||||
SplineSelection.Set(EditorSplineUtility.JoinKnots(m_KnotBuffer[0], m_KnotBuffer[1]));
|
||||
}
|
||||
|
||||
public void RefreshSelection(IReadOnlyList<SplineInfo> selectedSplines)
|
||||
{
|
||||
#if !UNITY_2023_2_OR_NEWER
|
||||
SplineSelection.GetElements(selectedSplines, m_KnotBuffer);
|
||||
|
||||
m_LinkButton.SetEnabled(SplineSelectionUtility.CanLinkKnots(m_KnotBuffer));
|
||||
m_UnlinkButton.SetEnabled(SplineSelectionUtility.CanUnlinkKnots(m_KnotBuffer));
|
||||
|
||||
m_SplitButton.SetEnabled(SplineSelectionUtility.CanSplitSelection(m_KnotBuffer));
|
||||
m_JoinButton.SetEnabled(SplineSelectionUtility.CanJoinSelection(m_KnotBuffer));
|
||||
|
||||
m_SelectedSplines = selectedSplines;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnReverseFlowClicked()
|
||||
{
|
||||
EditorSplineUtility.RecordSelection("Reverse Selected Splines Flow");
|
||||
EditorSplineUtility.ReverseSplinesFlow(m_SelectedSplines);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 077bfa6de2cd5634cb02f03ec290191d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SplineDataDictionary<>))]
|
||||
class SplineDataDictionaryPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorGUI.GetPropertyHeight(property.FindPropertyRelative("m_Data")) + EditorGUIUtility.standardVerticalSpacing;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.PropertyField(position, property.FindPropertyRelative("m_Data"), label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e526ebdb8c34af5b98d5eb45d4e56a5
|
||||
timeCreated: 1673297892
|
||||
@@ -0,0 +1,110 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(SplineData<>))]
|
||||
class SplineDataPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly string k_MultiSplineEditMessage = L10n.Tr("Multi-selection is not supported for SplineData");
|
||||
static readonly string k_DataUnitTooltip = L10n.Tr("The unit Data Points are using to be associated to the spline. 'Spline Distance' is " +
|
||||
"using the distance in Unity Units from the spline origin, 'Normalized Distance' is using a normalized value of the spline " +
|
||||
"length between [0,1] and Knot Index is using Spline Knot indices ");
|
||||
|
||||
readonly static GUIContent[] k_PathUnitIndexLabels = new[]
|
||||
{
|
||||
new GUIContent(L10n.Tr("Spline Distance")),
|
||||
new GUIContent(L10n.Tr("Normalized Distance")),
|
||||
new GUIContent(L10n.Tr("Knot Index"))
|
||||
};
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
float height = EditorGUIUtility.singleLineHeight;
|
||||
if (!property.isExpanded || property.serializedObject.isEditingMultipleObjects)
|
||||
return height;
|
||||
|
||||
//Adding space for the object field
|
||||
height += EditorGUIUtility.standardVerticalSpacing ;
|
||||
height += EditorGUI.GetPropertyHeight(property.FindPropertyRelative("m_DefaultValue")) + EditorGUIUtility.standardVerticalSpacing ;
|
||||
height += EditorGUI.GetPropertyHeight(property.FindPropertyRelative("m_IndexUnit")) + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
var datapointsProperty = property.FindPropertyRelative("m_DataPoints");
|
||||
height += EditorGUIUtility.singleLineHeight;
|
||||
if (datapointsProperty.isExpanded)
|
||||
{
|
||||
height += 2 * EditorGUIUtility.singleLineHeight;
|
||||
var arraySize = datapointsProperty.arraySize;
|
||||
if (arraySize == 0)
|
||||
{
|
||||
height += EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int dataPointIndex = 0; dataPointIndex < arraySize; dataPointIndex++)
|
||||
{
|
||||
height += datapointsProperty.GetArrayElementAtIndex(dataPointIndex).isExpanded
|
||||
? 3 * EditorGUIUtility.singleLineHeight + 2 * EditorGUIUtility.standardVerticalSpacing
|
||||
: EditorGUIUtility.singleLineHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
if (property.serializedObject.isEditingMultipleObjects)
|
||||
{
|
||||
EditorGUI.LabelField(position, L10n.Tr(k_MultiSplineEditMessage), EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
property.isExpanded = EditorGUI.Foldout(SplineGUIUtility.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), property.isExpanded, label);
|
||||
++EditorGUI.indentLevel;
|
||||
if (property.isExpanded)
|
||||
{
|
||||
SplineGUIUtility.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
|
||||
var valueProperty = property.FindPropertyRelative("m_DefaultValue");
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpace(EditorGUI.GetPropertyHeight(valueProperty), ref position), valueProperty);
|
||||
SplineGUIUtility.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
|
||||
var indexProperty = property.FindPropertyRelative("m_IndexUnit");
|
||||
var dataPointsProperty = property.FindPropertyRelative("m_DataPoints");
|
||||
var pathUnit = (PathIndexUnit)indexProperty.intValue;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var newPathUnit = EditorGUI.Popup(SplineGUIUtility.ReserveSpace(EditorGUI.GetPropertyHeight(indexProperty), ref position),
|
||||
new GUIContent("Data Index Unit", L10n.Tr(k_DataUnitTooltip)), (int)pathUnit, k_PathUnitIndexLabels);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (dataPointsProperty.arraySize == 0)
|
||||
indexProperty.intValue = newPathUnit;
|
||||
else
|
||||
{
|
||||
SplineDataConversionWindow.DoConfirmWindow(
|
||||
position,
|
||||
property,
|
||||
property.serializedObject.targetObject as Component,
|
||||
newPathUnit,
|
||||
false);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
SplineGUIUtility.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
|
||||
dataPointsProperty.isExpanded = EditorGUI.Foldout(SplineGUIUtility.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), dataPointsProperty.isExpanded, new GUIContent("Data Points"));
|
||||
if (dataPointsProperty.isExpanded)
|
||||
{
|
||||
SplineDataReorderableListUtility
|
||||
.GetDataPointsReorderableList(property, dataPointsProperty, pathUnit).DoList(position);
|
||||
}
|
||||
}
|
||||
--EditorGUI.indentLevel;
|
||||
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db00a1694ad7319449442045b8557a93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
static class SplineDataReorderableListUtility
|
||||
{
|
||||
readonly static string k_DataIndexTooltip = L10n.Tr("The index of the Data Point along the spline and the unit used.");
|
||||
readonly static string k_DataValueTooltip = L10n.Tr("The value of the Data Point.");
|
||||
|
||||
static Dictionary<string, ReorderableList> s_ReorderableLists = new Dictionary<string, ReorderableList>();
|
||||
static PathIndexUnit s_PathIndexUnit;
|
||||
readonly static string[] k_DisplayName = new string[] {"Dist","Path %","Knot"};
|
||||
|
||||
static SplineDataReorderableListUtility()
|
||||
{
|
||||
Selection.selectionChanged += ClearReorderableLists;
|
||||
}
|
||||
|
||||
static void ClearReorderableLists()
|
||||
{
|
||||
s_ReorderableLists.Clear();
|
||||
}
|
||||
|
||||
static void SetSplineDataDirty(SerializedProperty splineDataProperty)
|
||||
{
|
||||
var splineDataObject = SerializedPropertyUtility.GetSerializedPropertyObject(splineDataProperty);
|
||||
var dirtyMethod = splineDataObject.GetType().GetMethod("SetDirty", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
dirtyMethod?.Invoke(splineDataObject, null);
|
||||
}
|
||||
|
||||
public static ReorderableList GetDataPointsReorderableList(SerializedProperty splineDataProperty, SerializedProperty dataPointProperty, PathIndexUnit unit)
|
||||
{
|
||||
var key = dataPointProperty.propertyPath + splineDataProperty.serializedObject.targetObject.GetInstanceID();
|
||||
s_PathIndexUnit = unit;
|
||||
|
||||
if(s_ReorderableLists.TryGetValue(key, out var list))
|
||||
{
|
||||
try
|
||||
{
|
||||
SerializedProperty.EqualContents(list.serializedProperty, dataPointProperty);
|
||||
return list;
|
||||
}
|
||||
catch(NullReferenceException)
|
||||
{
|
||||
s_ReorderableLists.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
list = new ReorderableList(dataPointProperty.serializedObject, dataPointProperty, false, false, true, true);
|
||||
s_ReorderableLists.Add(key, list);
|
||||
|
||||
list.elementHeightCallback = (int index) =>
|
||||
{
|
||||
return dataPointProperty.arraySize > 0 && dataPointProperty.GetArrayElementAtIndex(index).isExpanded
|
||||
? 3 * EditorGUIUtility.singleLineHeight + 2 * EditorGUIUtility.standardVerticalSpacing
|
||||
: EditorGUIUtility.singleLineHeight;
|
||||
};
|
||||
|
||||
list.onChangedCallback = reorderableList =>
|
||||
{
|
||||
SetSplineDataDirty(splineDataProperty);
|
||||
};
|
||||
|
||||
list.drawElementCallback =
|
||||
(Rect position, int listIndex, bool isActive, bool isFocused) =>
|
||||
{
|
||||
var ppte = dataPointProperty.GetArrayElementAtIndex(listIndex);
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
var expended = EditorGUI.Foldout(SplineGUIUtility.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), ppte.isExpanded, new GUIContent($"Data Point [{listIndex}]"), true);
|
||||
if (expended != ppte.isExpanded)
|
||||
{
|
||||
ppte.isExpanded = expended;
|
||||
if (!isActive)
|
||||
list.index = listIndex;
|
||||
list.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
if (ppte.isExpanded)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
SplineGUIUtility.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var indexProperty = ppte.FindPropertyRelative("m_Index");
|
||||
EditorGUI.DelayedFloatField(SplineGUIUtility.ReserveSpace(EditorGUIUtility.singleLineHeight, ref position), indexProperty, new GUIContent($"Data Index ({k_DisplayName[(int)s_PathIndexUnit]})", L10n.Tr(k_DataIndexTooltip)));
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!isActive)
|
||||
return;
|
||||
|
||||
Undo.SetCurrentGroupName("Modify Spline Data point index");
|
||||
// Apply data point's index change - needs to be applied here so that the sort below is correct.
|
||||
dataPointProperty.serializedObject.ApplyModifiedProperties();
|
||||
var newIndex = ppte.FindPropertyRelative("m_Index").floatValue;
|
||||
|
||||
var splineDataObject = SerializedPropertyUtility.GetSerializedPropertyObject(splineDataProperty);
|
||||
var sortMethod = splineDataObject.GetType().GetMethod("ForceSort", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
sortMethod?.Invoke(splineDataObject, null);
|
||||
dataPointProperty.serializedObject.Update();
|
||||
for (int i = 0; i < dataPointProperty.arraySize; i++)
|
||||
{
|
||||
var index = dataPointProperty.GetArrayElementAtIndex(i).FindPropertyRelative("m_Index").floatValue;
|
||||
if (index == newIndex)
|
||||
{
|
||||
list.index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the changes resulting from data point sort.
|
||||
dataPointProperty.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
SetSplineDataDirty(splineDataProperty);
|
||||
}
|
||||
|
||||
SplineGUIUtility.ReserveSpace(EditorGUIUtility.standardVerticalSpacing, ref position);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var valueProperty = ppte.FindPropertyRelative("m_Value");
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpace(EditorGUI.GetPropertyHeight(valueProperty), ref position), valueProperty, new GUIContent("Data Value", L10n.Tr(k_DataValueTooltip)));
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.SetCurrentGroupName("Modify Spline Data point value");
|
||||
SetSplineDataDirty(splineDataProperty);
|
||||
if (!isActive)
|
||||
list.index = listIndex;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bcefd1f1914dff4dae9fe9c4250b8da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a property drawer for <see cref="SplineInfo"/> types.
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(SplineInfo))]
|
||||
public class SplineInfoPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the height of a SerializedProperty in pixels.
|
||||
/// </summary>
|
||||
/// <param name="property">The SerializedProperty to calculate height for.</param>
|
||||
/// <param name="label">The label of the SerializedProperty.</param>
|
||||
/// <returns>Returns the height of a SerializedProperty in pixels.</returns>
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorStyles.popup.CalcSize(label).y * 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an interface for a SerializedProperty with an integer property type.
|
||||
/// </summary>
|
||||
/// <param name="position">Rectangle on the screen to use for the property GUI.</param>
|
||||
/// <param name="property">The SerializedProperty to make the custom GUI for.</param>
|
||||
/// <param name="label">The label of this property.</param>
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var obj = property.FindPropertyRelative("m_Object");
|
||||
var con = property.FindPropertyRelative("m_Container");
|
||||
var ind = property.FindPropertyRelative("m_SplineIndex");
|
||||
|
||||
if (con.managedReferenceValue != null && obj.objectReferenceValue == null)
|
||||
EditorGUI.LabelField(SplineGUIUtility.ReserveSpaceForLine(ref position), "ISplineContainer",
|
||||
property.managedReferenceFieldTypename);
|
||||
else
|
||||
{
|
||||
EditorGUI.ObjectField(SplineGUIUtility.ReserveSpaceForLine(ref position), obj, typeof(ISplineContainer),
|
||||
new GUIContent("Spline Container"));
|
||||
}
|
||||
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpaceForLine(ref position), ind);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00a9f482c3791427aad5ae45ad463eda
|
||||
timeCreated: 1671639448
|
||||
@@ -0,0 +1,70 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(Spline))]
|
||||
class SplinePropertyDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly string[] k_SplineData = new string[]
|
||||
{
|
||||
"m_IntData",
|
||||
"m_FloatData",
|
||||
"m_Float4Data",
|
||||
"m_ObjectData"
|
||||
};
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (!property.isExpanded)
|
||||
return SplineGUIUtility.lineHeight;
|
||||
float height = SplineGUIUtility.lineHeight * 2;
|
||||
height += KnotReorderableList.Get(property).GetHeight();
|
||||
height += EditorGUIUtility.standardVerticalSpacing;
|
||||
for (int i = 0, c = k_SplineData.Length; i < c; ++i)
|
||||
height += EditorGUI.GetPropertyHeight(property.FindPropertyRelative(k_SplineData[i]));
|
||||
return height;
|
||||
}
|
||||
|
||||
// Important note - if this is inspecting a Spline that is not part of an ISplineContainer, callbacks will not
|
||||
// invoked. That means the Scene View won't reflect changes made in the Inspector without additional code to
|
||||
// fire the modified callbacks. The easiest way to handle this is to implement ISplineContainer. Alternatively,
|
||||
// write a custom editor for your class and call Spline.EnforceTangentModeNoNotify() & Spline.SetDirty() after
|
||||
// any changes.
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
property.isExpanded = EditorGUI.Foldout(
|
||||
SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref position),
|
||||
property.isExpanded, label);
|
||||
|
||||
if(property.isExpanded)
|
||||
{
|
||||
var closedProperty = property.FindPropertyRelative("m_Closed");
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(SplineGUIUtility.ReserveSpace(SplineGUIUtility.lineHeight, ref position), closedProperty);
|
||||
if (EditorGUI.EndChangeCheck() && SerializedPropertyUtility.TryGetSpline(property, out var spline))
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
spline.CheckAutoSmoothExtremityKnots();
|
||||
spline.SetDirty(SplineModification.ClosedModified);
|
||||
property.serializedObject.Update();
|
||||
}
|
||||
|
||||
var knots = KnotReorderableList.Get(property);
|
||||
knots.DoList(position);
|
||||
position.y += knots.GetHeight() + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
for (int i = 0, c = k_SplineData.Length; i < c; ++i)
|
||||
{
|
||||
var prop = property.FindPropertyRelative(k_SplineData[i]);
|
||||
var height = EditorGUI.GetPropertyHeight(prop);
|
||||
var rect = position;
|
||||
rect.height = height;
|
||||
position.y += rect.height;
|
||||
EditorGUI.PropertyField(rect, prop, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5dc161b954c4dd6a149b642c8e53bf9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
class SplineReorderableList : ReorderableList
|
||||
{
|
||||
SplineContainer m_Container;
|
||||
|
||||
static Dictionary<int, SplineReorderableList> s_Pool = new();
|
||||
|
||||
static SplineReorderableList()
|
||||
{
|
||||
Selection.selectionChanged += ClearPool;
|
||||
}
|
||||
|
||||
static int GetPropertyHash(SerializedProperty prop)
|
||||
{
|
||||
if(prop.serializedObject.targetObject == null)
|
||||
return 0;
|
||||
|
||||
unchecked
|
||||
{
|
||||
int hash = prop.serializedObject.targetObject.GetInstanceID();
|
||||
hash = hash * 31 * prop.propertyPath.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
static void ClearPool() => s_Pool.Clear();
|
||||
|
||||
public static SplineReorderableList Get(SerializedProperty splineArrayElement)
|
||||
{
|
||||
int hash = GetPropertyHash(splineArrayElement);
|
||||
if (!s_Pool.TryGetValue(hash, out var list))
|
||||
s_Pool.Add(hash, list = new SplineReorderableList(splineArrayElement.serializedObject, splineArrayElement));
|
||||
list.Init(splineArrayElement);
|
||||
return list;
|
||||
}
|
||||
|
||||
void Init(SerializedProperty splineArrayElement)
|
||||
{
|
||||
serializedProperty = splineArrayElement;
|
||||
if (splineArrayElement.serializedObject.targetObject is SplineContainer container)
|
||||
m_Container = container;
|
||||
}
|
||||
|
||||
public SplineReorderableList(
|
||||
SerializedObject serializedObject,
|
||||
SerializedProperty splineArrayElement) : base(serializedObject, splineArrayElement, true, false, true, true)
|
||||
{
|
||||
Init(splineArrayElement);
|
||||
multiSelect = true;
|
||||
elementHeightCallback += GetElementHeight;
|
||||
drawElementCallback += DrawElement;
|
||||
onReorderCallbackWithDetails += OnReorder;
|
||||
onSelectCallback += OnSelect;
|
||||
onAddCallback += OnAdd;
|
||||
onRemoveCallback += OnRemove;
|
||||
}
|
||||
|
||||
float GetElementHeight(int i)
|
||||
{
|
||||
return EditorGUI.GetPropertyHeight(serializedProperty.GetArrayElementAtIndex(i));
|
||||
}
|
||||
|
||||
void DrawElement(Rect position, int listIndex, bool isactive, bool isfocused)
|
||||
{
|
||||
++EditorGUI.indentLevel;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(position,
|
||||
serializedProperty.GetArrayElementAtIndex(listIndex),
|
||||
new GUIContent($"Spline {listIndex}"));
|
||||
EditorGUI.EndChangeCheck();
|
||||
--EditorGUI.indentLevel;
|
||||
}
|
||||
|
||||
void OnReorder(ReorderableList reorderableList, int srcIndex, int dstIndex)
|
||||
{
|
||||
if (m_Container == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(serializedProperty.serializedObject.targetObject, "Reordering Spline in SplineContainer");
|
||||
m_Container.ReorderSpline(srcIndex, dstIndex);
|
||||
serializedProperty.serializedObject.ApplyModifiedProperties();
|
||||
serializedProperty.serializedObject.Update();
|
||||
}
|
||||
|
||||
void OnSelect(ReorderableList _)
|
||||
{
|
||||
if (m_Container == null)
|
||||
return;
|
||||
SplineSelection.SetInspectorSelectedSplines(m_Container, selectedIndices);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
// Used in tests
|
||||
internal void OnAdd(ReorderableList _)
|
||||
{
|
||||
if(m_Container == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(serializedProperty.serializedObject.targetObject, "Add new Spline");
|
||||
|
||||
int added = 0;
|
||||
|
||||
if(selectedIndices.Count > 0)
|
||||
{
|
||||
foreach(var i in selectedIndices)
|
||||
{
|
||||
var spline = m_Container.AddSpline();
|
||||
spline.Copy(m_Container.Splines[i]);
|
||||
m_Container.CopyKnotLinks(i, m_Container.Splines.Count - 1);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var spline = m_Container.AddSpline();
|
||||
var srcSplineIndex = m_Container.Splines.Count - 2;
|
||||
if (srcSplineIndex >= 0)
|
||||
{
|
||||
spline.Copy(m_Container.Splines[srcSplineIndex]);
|
||||
m_Container.CopyKnotLinks(srcSplineIndex, m_Container.Splines.Count - 1);
|
||||
}
|
||||
added++;
|
||||
}
|
||||
|
||||
int maxCount = m_Container.Splines.Count;
|
||||
SelectRange(maxCount - added, maxCount - 1);
|
||||
onSelectCallback(this);
|
||||
}
|
||||
|
||||
// Used in tests
|
||||
internal void OnRemove(ReorderableList _)
|
||||
{
|
||||
if (m_Container == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(serializedProperty.serializedObject.targetObject, "Removing Spline from SplineContainer");
|
||||
|
||||
for (int i = selectedIndices.Count - 1; i >= 0; i--)
|
||||
m_Container.RemoveSplineAt(selectedIndices[i]);
|
||||
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(serializedProperty.serializedObject.targetObject);
|
||||
|
||||
ClearSelection();
|
||||
SplineSelection.ClearInspectorSelectedSplines();
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dda34381c8748a7a8d49edc59ba0847
|
||||
timeCreated: 1669140420
|
||||
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class TangentModePropertyField<T> : VisualElement
|
||||
where T : ISelectableElement
|
||||
{
|
||||
const string k_ButtonStripUssClass = "button-strip";
|
||||
const string k_ButtonStripButtonUssClass = k_ButtonStripUssClass + "-button";
|
||||
const string k_ButtonStripButtonLeftUssClass = k_ButtonStripButtonUssClass + "--left";
|
||||
const string k_ButtonStripButtonMiddleUssClass = k_ButtonStripButtonUssClass + "--middle";
|
||||
const string k_ButtonStripButtonRightUssClass = k_ButtonStripButtonUssClass + "--right";
|
||||
const string k_ButtonStripButtonIconUssClass = k_ButtonStripButtonUssClass + "__icon";
|
||||
const string k_ButtonStripButtonTextUssClass = k_ButtonStripButtonUssClass + "__text";
|
||||
const string k_ButtonStripButtonCheckedUssClass = k_ButtonStripButtonUssClass + "--checked";
|
||||
|
||||
static readonly SplineGUIUtility.EqualityComparer<T> s_Comparer = (a, b) =>
|
||||
GetModeForProperty(EditorSplineUtility.GetKnot(a).Mode) ==
|
||||
GetModeForProperty(EditorSplineUtility.GetKnot(b).Mode);
|
||||
|
||||
public event Action changed;
|
||||
|
||||
const TangentMode k_BezierMode = TangentMode.Mirrored;
|
||||
|
||||
readonly Button m_LinearButton;
|
||||
readonly Button m_AutoSmoothButton;
|
||||
readonly Button m_BezierButton;
|
||||
IReadOnlyList<T> m_Elements = new List<T>(0);
|
||||
TangentMode m_CurrentMode;
|
||||
|
||||
internal TangentModePropertyField()
|
||||
{
|
||||
AddToClassList(k_ButtonStripUssClass);
|
||||
|
||||
Add(m_LinearButton = CreateButton("Linear", L10n.Tr("Linear"), L10n.Tr("Tangents are pointing to the previous/next spline knot.")));
|
||||
m_LinearButton.AddToClassList(k_ButtonStripButtonLeftUssClass);
|
||||
m_LinearButton.clickable.clicked += () => OnValueChange(TangentMode.Linear);
|
||||
|
||||
Add(m_AutoSmoothButton = CreateButton("AutoSmooth", L10n.Tr("Auto"), L10n.Tr("Tangents are calculated using the previous and next knot positions.")));
|
||||
m_AutoSmoothButton.AddToClassList(k_ButtonStripButtonMiddleUssClass);
|
||||
m_AutoSmoothButton.clickable.clicked += () => OnValueChange(TangentMode.AutoSmooth);
|
||||
|
||||
Add(m_BezierButton = CreateButton("Bezier", L10n.Tr("Bezier"), L10n.Tr("Tangents are customizable and modifiable.")));
|
||||
m_BezierButton.AddToClassList(k_ButtonStripButtonRightUssClass);
|
||||
m_BezierButton.clickable.clicked += () => OnValueChange(TangentMode.Mirrored);
|
||||
}
|
||||
|
||||
static Button CreateButton(string name, string text, string tooltip)
|
||||
{
|
||||
var button = new Button{name = name};
|
||||
button.tooltip = tooltip;
|
||||
button.AddToClassList(k_ButtonStripButtonUssClass);
|
||||
|
||||
var icon = new VisualElement();
|
||||
icon.AddToClassList(k_ButtonStripButtonIconUssClass);
|
||||
icon.pickingMode = PickingMode.Ignore;
|
||||
button.Add(icon);
|
||||
|
||||
var label = new TextElement();
|
||||
label.AddToClassList(k_ButtonStripButtonTextUssClass);
|
||||
label.pickingMode = PickingMode.Ignore;
|
||||
label.text = text;
|
||||
button.Add(label);
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
public void Update(IReadOnlyList<T> targets)
|
||||
{
|
||||
m_Elements = targets;
|
||||
|
||||
if (SplineGUIUtility.HasMultipleValues(m_Elements, s_Comparer))
|
||||
SetToMixedValuesState();
|
||||
else
|
||||
SetValueWithoutNotify(EditorSplineUtility.GetKnot(targets[0]).Mode);
|
||||
}
|
||||
|
||||
void SetToMixedValuesState()
|
||||
{
|
||||
SetValueWithoutNotify((TangentMode)(-1));
|
||||
}
|
||||
|
||||
void SetValueWithoutNotify(TangentMode mode)
|
||||
{
|
||||
m_CurrentMode = GetModeForProperty(mode);
|
||||
|
||||
SetButtonChecked(m_LinearButton, m_CurrentMode == TangentMode.Linear);
|
||||
SetButtonChecked(m_AutoSmoothButton, m_CurrentMode == TangentMode.AutoSmooth);
|
||||
SetButtonChecked(m_BezierButton, m_CurrentMode == k_BezierMode);
|
||||
}
|
||||
void SetButtonChecked(Button button, bool isChecked)
|
||||
{
|
||||
button.EnableInClassList(k_ButtonStripButtonCheckedUssClass, isChecked);
|
||||
}
|
||||
|
||||
void OnValueChange(TangentMode mode)
|
||||
{
|
||||
if (GetModeForProperty(mode) == m_CurrentMode)
|
||||
return;
|
||||
|
||||
SetValueWithoutNotify(mode);
|
||||
|
||||
EditorSplineUtility.RecordSelection(SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
for (int i = 0; i < m_Elements.Count; ++i)
|
||||
{
|
||||
var knot = EditorSplineUtility.GetKnot(m_Elements[i]);
|
||||
if (m_Elements[i] is SelectableTangent tangent)
|
||||
knot.SetTangentMode(mode, (BezierTangent)tangent.TangentIndex);
|
||||
else
|
||||
{
|
||||
// Continuous mode should be prefered instead of Mirrored when switching from AutoSmooth to Bezier
|
||||
// as Centripetal Catmull-Rom's tangents are not always of equal length.
|
||||
if (knot.Mode == TangentMode.AutoSmooth && mode == TangentMode.Mirrored)
|
||||
knot.Mode = TangentMode.Continuous;
|
||||
else
|
||||
knot.Mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
changed?.Invoke();
|
||||
}
|
||||
|
||||
static TangentMode GetModeForProperty(TangentMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case TangentMode.Continuous:
|
||||
case TangentMode.Mirrored:
|
||||
case TangentMode.Broken:
|
||||
return k_BezierMode;
|
||||
|
||||
default:
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c74ddef7e6e74f529a51b5ad71552fbd
|
||||
timeCreated: 1637605071
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.UIElements;
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
class TangentPropertyField : VisualElement
|
||||
{
|
||||
static readonly List<float> s_LengthBuffer = new List<float>(0);
|
||||
static readonly SplineGUIUtility.EqualityComparer<float> s_MagnitudeComparer = (a, b) => a.Equals(b);
|
||||
|
||||
const string k_TangentFoldoutStyle = "tangent-drawer";
|
||||
const string k_TangentMagnitudeFloatFieldStyle = "tangent-magnitude-floatfield";
|
||||
|
||||
readonly BezierTangent m_Direction;
|
||||
readonly FloatField m_Magnitude;
|
||||
public readonly Float3PropertyField<SelectableKnot> vector3field;
|
||||
|
||||
IReadOnlyList<SelectableKnot> m_Elements = new List<SelectableKnot>(0);
|
||||
|
||||
public event Action changed;
|
||||
|
||||
public TangentPropertyField(string text, string vect3name, BezierTangent direction)
|
||||
{
|
||||
m_Direction = direction;
|
||||
|
||||
//Create Elements
|
||||
AddToClassList(k_TangentFoldoutStyle);
|
||||
AddToClassList("unity-base-field");
|
||||
|
||||
style.marginBottom = style.marginLeft = style.marginRight = style.marginTop = 0;
|
||||
|
||||
var foldout = new Foldout() { value = false };
|
||||
var foldoutToggle = foldout.Q<Toggle>();
|
||||
|
||||
m_Magnitude = new FloatField(L10n.Tr(text), 6);
|
||||
m_Magnitude.style.flexDirection = FlexDirection.Row;
|
||||
m_Magnitude.RemoveFromClassList("unity-base-field");
|
||||
vector3field = new Float3PropertyField<SelectableKnot>("", GetTangentPosition, ApplyPosition) { name = vect3name };
|
||||
|
||||
//Build UI Hierarchy
|
||||
Add(foldout);
|
||||
foldoutToggle.Add(m_Magnitude);
|
||||
foldout.Add(vector3field);
|
||||
foldout.Q<VisualElement>("unity-content").style.marginBottom = 0;
|
||||
|
||||
var field = m_Magnitude.Q<VisualElement>("unity-text-input");
|
||||
field.AddToClassList(k_TangentMagnitudeFloatFieldStyle);
|
||||
|
||||
vector3field.changed += () =>
|
||||
{
|
||||
Update(m_Elements);
|
||||
changed?.Invoke();
|
||||
};
|
||||
|
||||
m_Magnitude.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
var value = evt.newValue;
|
||||
if (evt.newValue < 0f)
|
||||
{
|
||||
m_Magnitude.SetValueWithoutNotify(0f);
|
||||
value = 0f;
|
||||
}
|
||||
|
||||
EditorSplineUtility.RecordObjects(m_Elements, SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
for (var i = 0; i < m_Elements.Count; ++i)
|
||||
{
|
||||
var knot = m_Elements[i];
|
||||
UpdateTangentMagnitude(new SelectableTangent(knot.SplineInfo, knot.KnotIndex, m_Direction), value, m_Direction == BezierTangent.In ? -1f : 1f);
|
||||
}
|
||||
|
||||
m_Magnitude.showMixedValue = false;
|
||||
Update(m_Elements);
|
||||
changed?.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(IReadOnlyList<SelectableKnot> elements)
|
||||
{
|
||||
m_Elements = elements;
|
||||
|
||||
s_LengthBuffer.Clear();
|
||||
for (int i = 0; i < elements.Count; ++i)
|
||||
s_LengthBuffer.Add(math.length((m_Direction == BezierTangent.In ? elements[i].TangentIn : elements[i].TangentOut).LocalPosition));
|
||||
|
||||
m_Magnitude.showMixedValue = SplineGUIUtility.HasMultipleValues(s_LengthBuffer, s_MagnitudeComparer);
|
||||
if (!m_Magnitude.showMixedValue)
|
||||
m_Magnitude.SetValueWithoutNotify(s_LengthBuffer[0]);
|
||||
|
||||
vector3field.Update(elements);
|
||||
}
|
||||
|
||||
float3 GetTangentPosition(SelectableKnot knot)
|
||||
{
|
||||
return new SelectableTangent(knot.SplineInfo, knot.KnotIndex, m_Direction).LocalPosition;
|
||||
}
|
||||
|
||||
void ApplyPosition(SelectableKnot knot, float3 position)
|
||||
{
|
||||
new SelectableTangent(knot.SplineInfo, knot.KnotIndex, m_Direction) { LocalPosition = position };
|
||||
}
|
||||
|
||||
void UpdateTangentMagnitude(SelectableTangent tangent, float value, float directionSign)
|
||||
{
|
||||
var direction = new float3(0, 0, directionSign);
|
||||
if (math.length(tangent.LocalDirection) > 0)
|
||||
direction = math.normalize(tangent.LocalDirection);
|
||||
|
||||
ElementInspector.ignoreKnotCallbacks = true;
|
||||
tangent.LocalPosition = value * direction;
|
||||
ElementInspector.ignoreKnotCallbacks = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26228ca4ee1f8eb44b0c159e0fc0ddec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
class SplineDataConversionWindow : EditorWindow
|
||||
{
|
||||
static readonly string k_SplineDataConversionMessage = L10n.Tr("Select a reference spline to convert your " +
|
||||
"SplineData with no data loss. Otherwise data " +
|
||||
"won't be converted.");
|
||||
|
||||
static readonly Regex k_EmbeddedSplineDataContainerIndex =
|
||||
new Regex(@"((?<=m_Splines\.Array\.data\[)([0-9]+))(?=\])", RegexOptions.Compiled);
|
||||
|
||||
SerializedProperty m_SplineDataProperty;
|
||||
SplineContainer m_TargetContainer;
|
||||
int m_TargetSpline;
|
||||
PathIndexUnit m_NewIndexUnit;
|
||||
string[] m_SplineSelectionContent;
|
||||
int[] m_SplineSelectionValues;
|
||||
|
||||
public static void DoConfirmWindow(Rect rect,
|
||||
SerializedProperty splineDataProperty,
|
||||
Component targetComponent,
|
||||
int newValue,
|
||||
bool forceShowDialog)
|
||||
{
|
||||
var window = Resources.FindObjectsOfTypeAll<SplineDataConversionWindow>().FirstOrDefault();
|
||||
if (window == null)
|
||||
window = CreateInstance<SplineDataConversionWindow>();
|
||||
window.titleContent = new GUIContent("Convert Spline Data Index Unit");
|
||||
window.m_SplineDataProperty = splineDataProperty;
|
||||
var(container, index) = FindPlausibleSplineContainer(targetComponent, splineDataProperty);
|
||||
window.SetTargetSpline(container, index);
|
||||
window.m_NewIndexUnit = (PathIndexUnit) newValue;
|
||||
window.minSize = new Vector2(400, 124);
|
||||
window.maxSize = new Vector2(400, 124);
|
||||
|
||||
if (forceShowDialog || container == null || index < 0)
|
||||
{
|
||||
window.ShowUtility();
|
||||
}
|
||||
else
|
||||
{
|
||||
window.ApplyConversion();
|
||||
DestroyImmediate(window);
|
||||
}
|
||||
}
|
||||
|
||||
void SetTargetSpline(SplineContainer container, int index)
|
||||
{
|
||||
m_TargetContainer = container;
|
||||
var splines = container == null ? Array.Empty<Spline>() : container.Splines;
|
||||
m_TargetSpline = math.min(math.max(0, index), splines.Count-1);
|
||||
m_SplineSelectionContent = new string[splines.Count];
|
||||
m_SplineSelectionValues = new int[splines.Count];
|
||||
for (int i = 0, c = splines.Count; i < c; ++i)
|
||||
{
|
||||
m_SplineSelectionContent[i] = $"Spline {i}";
|
||||
m_SplineSelectionValues[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Label(L10n.Tr("Spline Data Conversion"), EditorStyles.boldLabel);
|
||||
if(m_TargetContainer == null)
|
||||
EditorGUILayout.HelpBox(k_SplineDataConversionMessage,MessageType.Warning);
|
||||
else
|
||||
EditorGUILayout.HelpBox(L10n.Tr($"The spline {m_TargetContainer} will be used for data conversion."), MessageType.Info);
|
||||
|
||||
var container = m_TargetContainer;
|
||||
container = (SplineContainer)EditorGUILayout.ObjectField("Reference Spline",
|
||||
container,
|
||||
typeof(SplineContainer),
|
||||
true);
|
||||
if(container != m_TargetContainer)
|
||||
SetTargetSpline(container, m_TargetSpline);
|
||||
|
||||
m_TargetSpline = SplineGUILayout.SplineIndexPopup("Spline", m_TargetSpline, m_TargetContainer);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if(GUILayout.Button(new GUIContent(L10n.Tr("Convert"), L10n.Tr("Convert data indexes to the new Unit."))))
|
||||
{
|
||||
if(m_TargetContainer != null)
|
||||
ApplyConversion();
|
||||
Close();
|
||||
}
|
||||
|
||||
if(GUILayout.Button(L10n.Tr("Cancel")))
|
||||
Close();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
static (SplineContainer, int) FindPlausibleSplineContainer(Component targetComponent, SerializedProperty prop)
|
||||
{
|
||||
SplineContainer container = null;
|
||||
var fieldInfos = targetComponent.GetType().GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic);
|
||||
var providerFieldInfo = fieldInfos.FirstOrDefault(field => field.FieldType == typeof(SplineContainer));
|
||||
if(providerFieldInfo != null && providerFieldInfo.FieldType == typeof(SplineContainer))
|
||||
container = (SplineContainer)providerFieldInfo.GetValue(targetComponent);
|
||||
|
||||
if(container == null)
|
||||
container = targetComponent.gameObject.GetComponent<SplineContainer>();
|
||||
|
||||
int index = 0;
|
||||
|
||||
// if this is embedded SplineData, try to extract the spline index (if more than one spline)
|
||||
if (container != null && container.Splines.Count > 1)
|
||||
{
|
||||
// m_Splines.Array.data[0].m_ObjectData.m_Data.Array.data[0].Value.m_DataPoints
|
||||
var match = k_EmbeddedSplineDataContainerIndex.Match(prop.propertyPath);
|
||||
if (match.Success && int.TryParse(match.Value, out var i))
|
||||
index = i;
|
||||
}
|
||||
|
||||
return (container, index);
|
||||
}
|
||||
|
||||
void ApplyConversion()
|
||||
{
|
||||
m_SplineDataProperty.serializedObject.Update();
|
||||
ConvertPathUnit(m_SplineDataProperty, m_TargetContainer, m_TargetSpline, m_NewIndexUnit);
|
||||
m_SplineDataProperty.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
static void ConvertPathUnit(SerializedProperty splineData,
|
||||
ISplineContainer container,
|
||||
int splineIndex,
|
||||
PathIndexUnit newIndexUnit)
|
||||
{
|
||||
var spline = container.Splines[splineIndex];
|
||||
var transform = container is Component component
|
||||
? component.transform.localToWorldMatrix
|
||||
: Matrix4x4.identity;
|
||||
|
||||
using var native = new NativeSpline(spline, transform);
|
||||
var array = splineData.FindPropertyRelative("m_DataPoints");
|
||||
var pathUnit = splineData.FindPropertyRelative("m_IndexUnit");
|
||||
var from = (PathIndexUnit) Enum.GetValues(typeof(PathIndexUnit)).GetValue(pathUnit.enumValueIndex);
|
||||
|
||||
for (int i = 0, c = array.arraySize; i < c; ++i)
|
||||
{
|
||||
var point = array.GetArrayElementAtIndex(i);
|
||||
var index = point.FindPropertyRelative("m_Index");
|
||||
index.floatValue = native.ConvertIndexUnit(index.floatValue, from, newIndexUnit);
|
||||
}
|
||||
|
||||
pathUnit.enumValueIndex = (int) newIndexUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97db6026b4154fbf97b957dd2137810d
|
||||
timeCreated: 1634919075
|
||||
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides default handles to SplineData.
|
||||
/// Call <see cref="DataPointHandles"/> in your Editor Tool to add default handles
|
||||
/// for you to add, move, and remove SplineData's DataPoints along a spline.
|
||||
/// </summary>
|
||||
public static class SplineDataHandles
|
||||
{
|
||||
const float k_HandleSize = 0.15f;
|
||||
const int k_PickRes = 2;
|
||||
|
||||
static int[] s_DataPointsIDs;
|
||||
|
||||
static int s_NewDataPointIndex = -1;
|
||||
static float s_AddingDataPoint = float.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Creates manipulation handles in the SceneView to add, move, and remove SplineData's DataPoints along a spline.
|
||||
/// DataPoints of the targeted SplineData along a Spline. Left click on an empty location
|
||||
/// on the spline adds a new DataPoint in the SplineData. Left click on an existing DataPoint
|
||||
/// allows to move this point along the Spline while a right click on it allows to delete that DataPoint.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Left-click an empty location on the spline to add a new DataPoint to the SplineData.
|
||||
/// Left-click on a DataPoint to move the point along the Spline. Right-click a DataPoint to delete it.
|
||||
/// </remarks>
|
||||
/// <param name="spline">The Spline to use to interprete the SplineData.</param>
|
||||
/// <param name="splineData">The SplineData for which the handles are drawn.</param>
|
||||
/// <param name="useDefaultValueOnAdd">Either to use default value or closer DataPoint value when adding new DataPoint.</param>
|
||||
/// <typeparam name="TSpline">The Spline type.</typeparam>
|
||||
/// <typeparam name="TData">The type of data this data point stores.</typeparam>
|
||||
public static void DataPointHandles<TSpline, TData>(
|
||||
this TSpline spline,
|
||||
SplineData<TData> splineData,
|
||||
bool useDefaultValueOnAdd = false)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
spline.DataPointHandles(splineData, useDefaultValueOnAdd, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates manipulation handles in the Scene view that can be used to add, move, and remove SplineData's DataPoints along a spline.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Left-click an empty location on the spline to add a new DataPoint to the SplineData.
|
||||
/// Left-click and drag a DataPoint to move the point along the spline. Right-click a DataPoint to delete it.
|
||||
/// </remarks>
|
||||
/// <param name="spline">The spline to use to interprete the SplineData.</param>
|
||||
/// <param name="splineData">The SplineData for which the handles are drawn.</param>
|
||||
/// <param name="splineID">The ID for the spline.</param>
|
||||
/// <param name="useDefaultValueOnAdd">Whether to use the default value or a closer DataPoint value when adding new DataPoint.</param>
|
||||
/// <typeparam name="TSpline">The spline type.</typeparam>
|
||||
/// <typeparam name="TData">The type of data this data point stores.</typeparam>
|
||||
public static void DataPointHandles<TSpline, TData>(
|
||||
this TSpline spline,
|
||||
SplineData<TData> splineData,
|
||||
bool useDefaultValueOnAdd,
|
||||
int splineID = 0)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
var id = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
DataPointAddHandle(id, spline, splineData, useDefaultValueOnAdd, splineID);
|
||||
|
||||
// Draw Default manipulation handles
|
||||
DataPointMoveHandles(spline, splineData);
|
||||
|
||||
// Remove DataPoint functionality
|
||||
TryRemoveDataPoint(splineData);
|
||||
}
|
||||
|
||||
|
||||
static void TryRemoveDataPoint<TData>(SplineData<TData> splineData)
|
||||
{
|
||||
var evt = Event.current;
|
||||
//Remove data point only when not adding one and when using right click button
|
||||
if(float.IsNaN(s_AddingDataPoint) && GUIUtility.hotControl == 0
|
||||
&& evt.type == EventType.MouseDown && evt.button == 1
|
||||
&& s_DataPointsIDs.Contains(HandleUtility.nearestControl))
|
||||
{
|
||||
var dataPointIndex = splineData.Indexes.ElementAt(Array.IndexOf(s_DataPointsIDs, HandleUtility.nearestControl));
|
||||
splineData.RemoveDataPoint(dataPointIndex);
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsHotControl(float splineID)
|
||||
{
|
||||
return !float.IsNaN(s_AddingDataPoint) && splineID.Equals(s_AddingDataPoint);
|
||||
}
|
||||
|
||||
static void DataPointAddHandle<TSpline, TData>(
|
||||
int controlID,
|
||||
TSpline spline,
|
||||
SplineData<TData> splineData,
|
||||
bool useDefaultValueOnAdd,
|
||||
int splineID)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(controlID);
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
{
|
||||
if (!SplineHandles.ViewToolActive())
|
||||
{
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
SplineUtility.GetNearestPoint(spline, ray, out var pos, out _);
|
||||
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCircle(pos, 0.1f));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EventType.Repaint:
|
||||
if ((HandleUtility.nearestControl == controlID && GUIUtility.hotControl == 0 && float.IsNaN(s_AddingDataPoint)) || IsHotControl(splineID))
|
||||
{
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
SplineUtility.GetNearestPoint(spline, ray, out var pos, out var t);
|
||||
var upDir = spline.EvaluateUpVector(t);
|
||||
Handles.CircleHandleCap(controlID, pos, Quaternion.LookRotation(upDir), 0.15f * HandleUtility.GetHandleSize(pos), EventType.Repaint);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (evt.button == 0
|
||||
&& !SplineHandles.ViewToolActive()
|
||||
&& HandleUtility.nearestControl == controlID
|
||||
&& GUIUtility.hotControl == 0)
|
||||
{
|
||||
s_AddingDataPoint = splineID;
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
SplineUtility.GetNearestPoint(spline, ray, out _, out var t);
|
||||
var index = SplineUtility.ConvertIndexUnit(
|
||||
spline, t,
|
||||
splineData.PathIndexUnit);
|
||||
|
||||
s_NewDataPointIndex = splineData.AddDataPointWithDefaultValue(index, useDefaultValueOnAdd);
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (evt.button == 0 && IsHotControl(splineID))
|
||||
{
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
SplineUtility.GetNearestPoint(spline, ray, out _, out var t);
|
||||
var index = SplineUtility.ConvertIndexUnit(
|
||||
spline, t,
|
||||
splineData.PathIndexUnit);
|
||||
s_NewDataPointIndex = splineData.MoveDataPoint(s_NewDataPointIndex, index);
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (evt.button == 0 && IsHotControl(splineID))
|
||||
{
|
||||
s_AddingDataPoint = float.NaN;
|
||||
s_NewDataPointIndex = -1;
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void DataPointMoveHandles<TSpline, TData>(TSpline spline, SplineData<TData> splineData)
|
||||
where TSpline : ISpline
|
||||
{
|
||||
if(s_DataPointsIDs == null || s_DataPointsIDs.Length != splineData.Count)
|
||||
s_DataPointsIDs = new int[splineData.Count];
|
||||
|
||||
//Cache all data point IDs
|
||||
for(int dataIndex = 0; dataIndex < splineData.Count; dataIndex++)
|
||||
s_DataPointsIDs[dataIndex] = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
//Draw all data points handles on the spline
|
||||
for(int dataIndex = 0; dataIndex < splineData.Count; dataIndex++)
|
||||
{
|
||||
var index = splineData.Indexes.ElementAt(dataIndex);
|
||||
SplineDataHandle(
|
||||
s_DataPointsIDs[dataIndex],
|
||||
spline,
|
||||
splineData,
|
||||
index,
|
||||
k_HandleSize,
|
||||
out float newIndex);
|
||||
|
||||
if(GUIUtility.hotControl == s_DataPointsIDs[dataIndex])
|
||||
{
|
||||
var newDataIndex = splineData.MoveDataPoint(dataIndex, newIndex);
|
||||
// If the current DataPoint is moved across another DataPoint, then update the hotControl ID
|
||||
if(newDataIndex - index != 0)
|
||||
GUIUtility.hotControl = s_DataPointsIDs[newDataIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SplineDataHandle<TSpline, TData>(
|
||||
int controlID,
|
||||
TSpline spline,
|
||||
SplineData<TData> splineData,
|
||||
float dataPointIndex,
|
||||
float size,
|
||||
out float newTime) where TSpline : ISpline
|
||||
{
|
||||
newTime = dataPointIndex;
|
||||
|
||||
Event evt = Event.current;
|
||||
EventType eventType = evt.GetTypeForControl(controlID);
|
||||
|
||||
var normalizedT = SplineUtility.GetNormalizedInterpolation(spline, dataPointIndex, splineData.PathIndexUnit);
|
||||
var dataPosition = SplineUtility.EvaluatePosition(spline, normalizedT);
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case EventType.Layout:
|
||||
var dist = HandleUtility.DistanceToCircle(dataPosition, size * HandleUtility.GetHandleSize(dataPosition));
|
||||
HandleUtility.AddControl(controlID, dist);
|
||||
break;
|
||||
|
||||
case EventType.Repaint:
|
||||
DrawSplineDataHandle(controlID, dataPosition, size);
|
||||
break;
|
||||
|
||||
case EventType.MouseDown:
|
||||
if (evt.button == 0
|
||||
&& !SplineHandles.ViewToolActive()
|
||||
&& HandleUtility.nearestControl == controlID
|
||||
&& GUIUtility.hotControl == 0)
|
||||
{
|
||||
GUIUtility.hotControl = controlID;
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == controlID)
|
||||
{
|
||||
if(evt.button == 0)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
newTime = GetClosestSplineDataT(spline, splineData);
|
||||
}
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseMove:
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawSplineDataHandle(int controlID, Vector3 position, float size)
|
||||
{
|
||||
var handleColor = Handles.color;
|
||||
if(controlID == GUIUtility.hotControl)
|
||||
handleColor = Handles.selectedColor;
|
||||
else if(GUIUtility.hotControl == 0 && controlID == HandleUtility.nearestControl)
|
||||
handleColor = Handles.preselectionColor;
|
||||
|
||||
// to avoid affecting the sphere dimensions with the handles matrix, we'll just use the position and reset
|
||||
// the matrix to identity when drawing.
|
||||
position = Handles.matrix * position;
|
||||
|
||||
using(new Handles.DrawingScope(handleColor, Matrix4x4.identity))
|
||||
{
|
||||
Handles.SphereHandleCap(
|
||||
controlID,
|
||||
position,
|
||||
Quaternion.identity,
|
||||
size * HandleUtility.GetHandleSize(position),
|
||||
EventType.Repaint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Spline must be in world space
|
||||
static float GetClosestSplineDataT<TSpline,TData>(TSpline spline, SplineData<TData> splineData) where TSpline : ISpline
|
||||
{
|
||||
var evt = Event.current;
|
||||
var ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition);
|
||||
|
||||
SplineUtility.GetNearestPoint(spline,
|
||||
ray,
|
||||
out float3 _,
|
||||
out float t,
|
||||
k_PickRes);
|
||||
|
||||
return SplineUtility.ConvertIndexUnit(spline, t, splineData.PathIndexUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bea6b12ab5854760b485bc57408b9c3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains IMGUI controls for editing Spline data.
|
||||
/// </summary>
|
||||
public static class SplineGUI
|
||||
{
|
||||
static GUIContent s_TempContent = new GUIContent();
|
||||
static string[] s_SplineIndexPopupContents = new string[1] { "Spline 0" };
|
||||
|
||||
internal static GUIContent TempContent(string label, string tooltip = null, Texture2D image = null)
|
||||
{
|
||||
s_TempContent.text = label;
|
||||
s_TempContent.tooltip = tooltip;
|
||||
s_TempContent.image = image;
|
||||
return s_TempContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select an index between 0 and the count of <see cref="Splines"/> contained in the
|
||||
/// provided <paramref name="container"/>.
|
||||
/// </summary>
|
||||
/// <param name="container">A <see cref="SplineContainer"/> that determines how many splines are available in
|
||||
/// the popup selector.</param>
|
||||
/// <param name="label">The label to use for this property. If null, the property display name is used.</param>
|
||||
/// <param name="rect">The rectangle on the screen to use for the field.</param>
|
||||
/// <param name="property">A SerializedProperty that stores an integer value.</param>
|
||||
/// <exception cref="ArgumentException">An exception is thrown if <paramref name="property"/> is not an integer
|
||||
/// field.</exception>
|
||||
/// <typeparam name="T">The type implementing <see cref="ISplineContainer"/>.</typeparam>
|
||||
public static void SplineIndexField<T>(Rect rect, SerializedProperty property, GUIContent label, T container) where T : ISplineContainer
|
||||
{
|
||||
SplineIndexField(rect, property, label, container == null ? 0 : container.Splines.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select an index between 0 and <paramref name="splineCount"/>.
|
||||
/// </summary>
|
||||
/// <param name="rect">The rectangle on the screen to use for the field.</param>
|
||||
/// <param name="property">A SerializedProperty that stores an integer value.</param>
|
||||
/// <param name="label">The label to use for this property. If null, the property display name is used.</param>
|
||||
/// <param name="splineCount">The number of splines available. In most cases, this is the size of
|
||||
/// <see cref="SplineContainer.Splines"/></param>
|
||||
/// <exception cref="ArgumentException">An exception is thrown if <paramref name="property"/> is not an integer
|
||||
/// field.</exception>
|
||||
public static void SplineIndexField(Rect rect, SerializedProperty property, GUIContent label, int splineCount)
|
||||
{
|
||||
if (property.propertyType != SerializedPropertyType.Integer)
|
||||
throw new ArgumentException("Spline index property must be of type `int`.", nameof(property));
|
||||
EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
|
||||
property.intValue = SplineIndexPopup(rect, label == null ? property.displayName : label.text, property.intValue, splineCount);
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select an index between 0 and <paramref name="splineCount"/>.
|
||||
/// </summary>
|
||||
/// <param name="label">An optional prefix label.</param>
|
||||
/// <param name="splineCount">The number of splines available. In most cases, this is the size of
|
||||
/// <see cref="SplineContainer.Splines"/></param>
|
||||
/// <param name="rect">The rectangle on the screen to use for the field.</param>
|
||||
/// <param name="index">The current index.</param>
|
||||
/// <returns>The selected index.</returns>
|
||||
public static int SplineIndexPopup(Rect rect, string label, int index, int splineCount)
|
||||
{
|
||||
if (splineCount != s_SplineIndexPopupContents.Length)
|
||||
{
|
||||
Array.Resize(ref s_SplineIndexPopupContents, splineCount);
|
||||
for (int i = 0; i < splineCount; ++i)
|
||||
s_SplineIndexPopupContents[i] = $"Spline {i}";
|
||||
}
|
||||
|
||||
return Math.Min(
|
||||
Math.Max(0, EditorGUI.IntPopup(rect, label, index, s_SplineIndexPopupContents, null)),
|
||||
splineCount-1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides IMGUI controls to edit Spline data.
|
||||
/// </summary>
|
||||
public static class SplineGUILayout
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select an index between 0 and the count of <see cref="Splines"/> contained in the
|
||||
/// provided <paramref name="container"/>.
|
||||
/// </summary>
|
||||
/// <param name="container">A <see cref="SplineContainer"/> that determines how many splines are available in
|
||||
/// the popup selector.</param>
|
||||
/// <param name="property">A SerializedProperty that stores an integer value.</param>
|
||||
/// <exception cref="ArgumentException">An exception is thrown if <paramref name="property"/> is not an integer
|
||||
/// field.</exception>
|
||||
/// <typeparam name="T">The type implementing <see cref="ISplineContainer"/>.</typeparam>
|
||||
public static void SplineIndexField<T>(SerializedProperty property, T container) where T : ISplineContainer
|
||||
{
|
||||
SplineIndexField(property, container == null ? 0 : container.Splines.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select an index between 0 and <paramref name="splineCount"/>.
|
||||
/// </summary>
|
||||
/// <param name="property">A SerializedProperty that stores an integer value.</param>
|
||||
/// <param name="splineCount">The number of splines available. In most cases, this is the size of
|
||||
/// <see cref="SplineContainer.Splines"/></param>
|
||||
/// <exception cref="ArgumentException">An exception is thrown if <paramref name="property"/> is not an integer
|
||||
/// field.</exception>
|
||||
public static void SplineIndexField(SerializedProperty property, int splineCount)
|
||||
{
|
||||
if (property.propertyType != SerializedPropertyType.Integer)
|
||||
throw new ArgumentException("Spline index property must be of type `int`.", nameof(property));
|
||||
EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
|
||||
property.intValue = SplineIndexPopup(property.displayName, property.intValue, splineCount);
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select a spline index relative to <paramref name="container"/>.
|
||||
/// </summary>
|
||||
/// <param name="label">An optional prefix label.</param>
|
||||
/// <param name="index">The current index.</param>
|
||||
/// <param name="container">A <see cref="SplineContainer"/> that determines how many splines are available in
|
||||
/// the popup selector.</param>
|
||||
/// <typeparam name="T">The type of <see cref="ISplineContainer"/>.</typeparam>
|
||||
/// <returns>The selected index.</returns>
|
||||
public static int SplineIndexPopup<T>(string label, int index, T container) where T : ISplineContainer
|
||||
{
|
||||
return SplineIndexPopup(label, index, container == null ? 0 : container.Splines.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dropdown to select an index between 0 and <paramref name="splineCount"/>.
|
||||
/// </summary>
|
||||
/// <param name="label">An optional prefix label.</param>
|
||||
/// <param name="splineCount">The number of splines available. In most cases, this is the size of
|
||||
/// <see cref="SplineContainer.Splines"/></param>
|
||||
/// <param name="index">The current index.</param>
|
||||
/// <returns>The selected index.</returns>
|
||||
public static int SplineIndexPopup(string label, int index, int splineCount)
|
||||
{
|
||||
var rect = GUILayoutUtility.GetRect(SplineGUI.TempContent(label), EditorStyles.popup);
|
||||
return SplineGUI.SplineIndexPopup(rect, label, index, splineCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a field for an embedded <see cref="SplineData{T}"/> property. Embedded <see cref="SplineData{T}"/>
|
||||
/// is stored in the <see cref="Spline"/> class and can be accessed through a string key value. Use this
|
||||
/// function to expose an embedded <see cref="SplineData{T}"/> through the Inspector.
|
||||
/// </summary>
|
||||
/// <param name="container">The <see cref="SplineContainer"/> that holds the <see cref="Spline"/> target.</param>
|
||||
/// <param name="index">The index of the target <see cref="Spline"/> in the <see cref="SplineContainer.Splines"/>
|
||||
/// array.</param>
|
||||
/// <param name="type">The <see cref="EmbeddedSplineDataType"/> type of data stored in the embedded
|
||||
/// <see cref="SplineData{T}"/></param>
|
||||
/// <param name="key">A string value used to identify and access embedded <see cref="SplineData{T}"/>.</param>
|
||||
/// <returns>True if the property has children, is expanded, and includeChildren was set to false. Returns false otherwise.</returns>
|
||||
public static bool EmbeddedSplineDataField(SplineContainer container, int index, EmbeddedSplineDataType type, string key)
|
||||
{
|
||||
return EmbeddedSplineDataField(null, container, index, type, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a field for an embedded <see cref="SplineData{T}"/> property. Embedded <see cref="SplineData{T}"/>
|
||||
/// is stored in the <see cref="Spline"/> class and can be accessed through a string key value. Use this
|
||||
/// function to expose an embedded <see cref="SplineData{T}"/> through the Inspector.
|
||||
/// </summary>
|
||||
/// <param name="label">An optional prefix label.</param>
|
||||
/// <param name="container">The <see cref="SplineContainer"/> that holds the <see cref="Spline"/> target.</param>
|
||||
/// <param name="index">The index of the target <see cref="Spline"/> in the <see cref="SplineContainer.Splines"/>
|
||||
/// array.</param>
|
||||
/// <param name="type">The <see cref="EmbeddedSplineDataType"/> type of data stored in the embedded
|
||||
/// <see cref="SplineData{T}"/></param>
|
||||
/// <param name="key">A string value used to identify and access embedded <see cref="SplineData{T}"/>.</param>
|
||||
/// <returns>True if the property has children, is expanded, and includeChildren was set to false. Returns false otherwise.</returns>
|
||||
public static bool EmbeddedSplineDataField(GUIContent label,
|
||||
SplineContainer container,
|
||||
int index,
|
||||
EmbeddedSplineDataType type,
|
||||
string key)
|
||||
{
|
||||
if (container == null || index < 0 || index >= container.Splines.Count)
|
||||
return false;
|
||||
|
||||
var property = SerializedPropertyUtility.GetEmbeddedSplineDataProperty(container, index, type, key);
|
||||
|
||||
property.serializedObject.Update();
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var ret = EditorGUILayout.PropertyField(property, label);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
return ret;
|
||||
}
|
||||
|
||||
internal static void QuaternionField(Rect rect, GUIContent content, SerializedProperty property)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Quaternion value = SplineGUIUtility.GetQuaternionValue(property);
|
||||
var result = EditorGUI.Vector3Field(rect, content, value.eulerAngles);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SplineGUIUtility.SetQuaternionValue(property, Quaternion.Euler(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54bfd57b67ce80c4481a2c3616fb0e7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class SplineGUIUtility
|
||||
{
|
||||
public delegate bool EqualityComparer<in T>(T a, T b);
|
||||
|
||||
internal static readonly float lineHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
public static bool HasMultipleValues<T>(IReadOnlyList<T> elements, EqualityComparer<T> comparer)
|
||||
{
|
||||
if (elements.Count < 2)
|
||||
return false;
|
||||
|
||||
var first = elements[0];
|
||||
for (int i = 1; i < elements.Count; ++i)
|
||||
if (!comparer.Invoke(first, elements[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static quaternion GetQuaternionValue(SerializedProperty property)
|
||||
{
|
||||
return new quaternion(
|
||||
property.FindPropertyRelative("value.x").floatValue,
|
||||
property.FindPropertyRelative("value.y").floatValue,
|
||||
property.FindPropertyRelative("value.z").floatValue,
|
||||
property.FindPropertyRelative("value.w").floatValue);
|
||||
}
|
||||
|
||||
public static void SetQuaternionValue(SerializedProperty property, Quaternion value)
|
||||
{
|
||||
property.FindPropertyRelative("value.x").floatValue = value.x;
|
||||
property.FindPropertyRelative("value.y").floatValue = value.y;
|
||||
property.FindPropertyRelative("value.z").floatValue = value.z;
|
||||
property.FindPropertyRelative("value.w").floatValue = value.w;
|
||||
}
|
||||
|
||||
public static SerializedProperty GetParentSplineProperty(SerializedProperty property)
|
||||
{
|
||||
var properties = property.propertyPath.Split('.');
|
||||
if (properties.Length == 0)
|
||||
return null;
|
||||
|
||||
var current = property.serializedObject.FindProperty(properties[0]);
|
||||
|
||||
for (var i = 1; i < properties.Length; ++i)
|
||||
{
|
||||
var p = properties[i];
|
||||
if (current.type == typeof(Spline).Name)
|
||||
return current;
|
||||
|
||||
if (current.propertyType == SerializedPropertyType.ManagedReference
|
||||
&& current.managedReferenceFullTypename == typeof(Spline).AssemblyQualifiedName)
|
||||
return current;
|
||||
|
||||
current = current.FindPropertyRelative(p);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Rect ReserveSpace(float height, ref Rect total)
|
||||
{
|
||||
Rect current = total;
|
||||
current.height = height;
|
||||
total.y += height;
|
||||
return current;
|
||||
}
|
||||
|
||||
public static Rect ReserveSpaceForLine(ref Rect total)
|
||||
{
|
||||
var height = EditorGUIUtility.wideMode ? lineHeight : 2f * lineHeight;
|
||||
return ReserveSpace(height, ref total);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf6af609234b6af4f8218e694d4895c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
/// <summary>
|
||||
/// A PropertyDrawer used to show a popup menu with available spline indices relative to a <see cref="ISplineContainer"/>.
|
||||
/// Add <see cref="UnityEngine.Splines.SplineIndexAttribute"/> to a serialized integer type to use.
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(SplineIndexAttribute))]
|
||||
public class SplineIndexPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
static readonly int[] k_MissingContainerValues = new int[] { 0 };
|
||||
static readonly GUIContent[] k_MissingContainerContent = new GUIContent[] { new GUIContent("") };
|
||||
|
||||
string GetWarningMessage(SplineIndexAttribute attrib) =>
|
||||
$"SplineIndex property attribute does not reference a valid SplineContainer: \"{attrib.SplineContainerProperty}\"";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the height of a SerializedProperty in pixels.
|
||||
/// </summary>
|
||||
/// <param name="property">The SerializedProperty to calculate height for.</param>
|
||||
/// <param name="label">The label of the SerializedProperty.</param>
|
||||
/// <returns>Returns the height of a SerializedProperty in pixels.</returns>
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
return EditorStyles.popup.CalcSize(label).y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an interface for a SerializedProperty with an integer property type.
|
||||
/// </summary>
|
||||
/// <param name="position">Rectangle on the screen to use for the property GUI.</param>
|
||||
/// <param name="property">The SerializedProperty to make the custom GUI for.</param>
|
||||
/// <param name="label">The label of this property.</param>
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
if (property.propertyType != SerializedPropertyType.Integer || attribute is not SplineIndexAttribute attrib)
|
||||
return;
|
||||
|
||||
var path = property.propertyPath.Replace(property.name, attrib.SplineContainerProperty);
|
||||
var container = property.serializedObject.FindProperty(path);
|
||||
|
||||
if (container == null || !(container.objectReferenceValue is ISplineContainer res))
|
||||
{
|
||||
new EditorGUI.DisabledScope(true);
|
||||
EditorGUI.IntPopup(position, label, 0, k_MissingContainerContent, k_MissingContainerValues);
|
||||
}
|
||||
else
|
||||
SplineGUI.SplineIndexField(position, property, label, res.Splines.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d65cf8364d644f1a82a62a1c0272310
|
||||
timeCreated: 1671553991
|
||||
@@ -0,0 +1,83 @@
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine;
|
||||
using UnityEditor.EditorTools;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
static class SplineMenu
|
||||
{
|
||||
const string k_MenuPath = "GameObject/Spline";
|
||||
|
||||
internal static GameObject CreateSplineGameObject(MenuCommand menuCommand, Spline spline = null)
|
||||
{
|
||||
var name = GameObjectUtility.GetUniqueNameForSibling(null, "Spline");
|
||||
var gameObject = ObjectFactory.CreateGameObject(name, typeof(SplineContainer));
|
||||
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
ObjectFactory.PlaceGameObject(gameObject, menuCommand.context as GameObject);
|
||||
#else
|
||||
if (menuCommand.context is GameObject go)
|
||||
{
|
||||
Undo.RecordObject(gameObject.transform, "Re-parenting");
|
||||
gameObject.transform.SetParent(go.transform);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (spline != null)
|
||||
{
|
||||
var container = gameObject.GetComponent<SplineContainer>();
|
||||
container.Spline = spline;
|
||||
}
|
||||
|
||||
Selection.activeGameObject = gameObject;
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
const int k_MenuPriority = 10;
|
||||
|
||||
[MenuItem(k_MenuPath + "/Draw Splines Tool...", false, k_MenuPriority + 0)]
|
||||
static void CreateNewSpline(MenuCommand menuCommand)
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateSplineTool>();
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Square", false, k_MenuPriority + 11)]
|
||||
static void CreateSquare(MenuCommand command)
|
||||
{
|
||||
CreateSplineGameObject(command, SplineFactory.CreateSquare(1f));
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Rounded Square", false, k_MenuPriority + 12)]
|
||||
static void CreateRoundedSquare(MenuCommand command)
|
||||
{
|
||||
CreateSplineGameObject(command, SplineFactory.CreateRoundedCornerSquare(1f, 0.25f));
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Circle", false, k_MenuPriority + 13)]
|
||||
static void CreateCircle(MenuCommand command)
|
||||
{
|
||||
CreateSplineGameObject(command, SplineFactory.CreateCircle(0.5f));
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Polygon", false, k_MenuPriority + 14)]
|
||||
static void CreatePolygon(MenuCommand command)
|
||||
{
|
||||
var edgeSize = math.sin(math.PI / 6f);
|
||||
CreateSplineGameObject(command, SplineFactory.CreatePolygon(edgeSize, 6));
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Helix", false, k_MenuPriority + 15)]
|
||||
static void CreateHelix(MenuCommand command)
|
||||
{
|
||||
CreateSplineGameObject(command, SplineFactory.CreateHelix(0.5f, 1f, 1));
|
||||
}
|
||||
|
||||
[MenuItem(k_MenuPath + "/Star", false, k_MenuPriority + 16)]
|
||||
static void CreateStar(MenuCommand command)
|
||||
{
|
||||
var edgeSize = math.sin(math.PI / 5f);
|
||||
CreateSplineGameObject(command, SplineFactory.CreateStarPolygon(edgeSize, 5, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b9129ed6a1eebe48b297bed06a9694e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e21391b51918435b9e07d2f917a37c79
|
||||
timeCreated: 1675955599
|
||||
@@ -0,0 +1,113 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEngine.Splines;
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class BezierKnotDrawer : ElementDrawer<SelectableKnot>
|
||||
{
|
||||
static readonly string k_PositionTooltip = L10n.Tr("Knot Position");
|
||||
static readonly string k_RotationTooltip = L10n.Tr("Knot Rotation");
|
||||
|
||||
readonly Float3PropertyField<SelectableKnot> m_Position;
|
||||
readonly Float3PropertyField<SelectableKnot> m_Rotation;
|
||||
readonly TangentModePropertyField<SelectableKnot> m_Mode;
|
||||
readonly BezierTangentPropertyField<SelectableKnot> m_BezierMode;
|
||||
readonly TangentPropertyField m_TangentIn;
|
||||
readonly TangentPropertyField m_TangentOut;
|
||||
|
||||
public BezierKnotDrawer()
|
||||
{
|
||||
VisualElement row;
|
||||
Add(row = new VisualElement(){name = "Vector3WithIcon"});
|
||||
row.tooltip = k_PositionTooltip;
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.Add(new VisualElement(){name = "PositionIcon"});
|
||||
row.Add(m_Position = new Float3PropertyField<SelectableKnot>("",
|
||||
(knot) => knot.LocalPosition,
|
||||
(knot, value) => knot.LocalPosition = value)
|
||||
{ name = "Position" });
|
||||
|
||||
m_Position.style.flexGrow = 1;
|
||||
|
||||
Add(row = new VisualElement(){name = "Vector3WithIcon"});
|
||||
row.tooltip = k_RotationTooltip;
|
||||
row.style.flexDirection = FlexDirection.Row;
|
||||
row.Add(new VisualElement(){name = "RotationIcon"});
|
||||
row.Add(m_Rotation = new Float3PropertyField<SelectableKnot>("",
|
||||
(knot) => ((Quaternion)knot.LocalRotation).eulerAngles,
|
||||
(knot, value) => knot.LocalRotation = Quaternion.Euler(value))
|
||||
{ name = "Rotation" });
|
||||
|
||||
m_Rotation.style.flexGrow = 1;
|
||||
|
||||
Add(new Separator());
|
||||
|
||||
Add(m_Mode = new TangentModePropertyField<SelectableKnot>());
|
||||
m_Mode.changed += Update;
|
||||
|
||||
Add(m_BezierMode = new BezierTangentPropertyField<SelectableKnot>());
|
||||
m_BezierMode.changed += Update;
|
||||
|
||||
Add(m_TangentIn = new TangentPropertyField("In", "TangentIn", BezierTangent.In));
|
||||
Add(m_TangentOut = new TangentPropertyField("Out", "TangentOut", BezierTangent.Out));
|
||||
|
||||
//Update opposite to take into account some tangent modes
|
||||
m_TangentIn.changed += () => m_TangentOut.Update(targets);
|
||||
m_TangentOut.changed += () => m_TangentIn.Update(targets);
|
||||
}
|
||||
|
||||
public override string GetLabelForTargets()
|
||||
{
|
||||
if (targets.Count > 1)
|
||||
return $"<b>({targets.Count}) Knots</b> selected";
|
||||
|
||||
return $"<b>Knot {target.KnotIndex}</b> (<b>Spline {target.SplineInfo.Index}</b>) selected";
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
m_Position.Update(targets);
|
||||
m_Rotation.Update(targets);
|
||||
|
||||
m_Mode.Update(targets);
|
||||
m_BezierMode.Update(targets);
|
||||
|
||||
m_TangentIn.Update(targets);
|
||||
m_TangentOut.Update(targets);
|
||||
|
||||
//Disabling edition when using linear tangents
|
||||
UpdateTangentsState();
|
||||
}
|
||||
|
||||
void UpdateTangentsState()
|
||||
{
|
||||
bool tangentsModifiable = true;
|
||||
bool tangentsBroken = true;
|
||||
bool tangentInSelectable = false;
|
||||
bool tangentOutSelectable = false;
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
var mode = targets[i].Mode;
|
||||
tangentsModifiable &= SplineUtility.AreTangentsModifiable(mode);
|
||||
tangentsBroken &= mode == TangentMode.Broken;
|
||||
tangentInSelectable |= SplineSelectionUtility.IsSelectable(targets[i].TangentIn);
|
||||
tangentOutSelectable |= SplineSelectionUtility.IsSelectable(targets[i].TangentOut);
|
||||
}
|
||||
|
||||
m_TangentIn.SetEnabled(tangentsModifiable && tangentInSelectable);
|
||||
m_TangentOut.SetEnabled(tangentsModifiable && tangentOutSelectable);
|
||||
|
||||
if(tangentsModifiable)
|
||||
{
|
||||
m_TangentIn.vector3field.SetEnabled(tangentsBroken);
|
||||
m_TangentOut.vector3field.SetEnabled(tangentsBroken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1dec5e4d3c4dd84aab4dfbb3c8e6b48
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using UnityEditor.Toolbars;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines.Editor.GUI
|
||||
{
|
||||
[EditorToolbarElement("Spline Tool Settings/Default Knot Type")]
|
||||
class DefaultKnotTypeDropdown : EditorToolbarDropdown
|
||||
{
|
||||
const string k_LinearIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/Tangent_Linear.png";
|
||||
const string k_AutoSmoothIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/AutoSmoothKnot.png";
|
||||
readonly GUIContent[] m_OptionContents = new GUIContent[2];
|
||||
|
||||
public DefaultKnotTypeDropdown()
|
||||
{
|
||||
name = "Default Knot Type";
|
||||
|
||||
var content = EditorGUIUtility.TrTextContent("Linear",
|
||||
"Tangents are not used. A linear knot tries to connect to another by a path with no curvature.",
|
||||
k_LinearIconPath);
|
||||
m_OptionContents[0] = content;
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Auto Smooth",
|
||||
"Tangents are calculated using the previous and next knot positions.",
|
||||
k_AutoSmoothIconPath);
|
||||
m_OptionContents[1] = content;
|
||||
|
||||
clicked += OpenContextMenu;
|
||||
|
||||
RefreshElementContent();
|
||||
}
|
||||
|
||||
void OpenContextMenu()
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
menu.AddItem(m_OptionContents[0], EditorSplineUtility.DefaultTangentMode == TangentMode.Linear,
|
||||
() => SetTangentModeIfNeeded(TangentMode.Linear));
|
||||
|
||||
menu.AddItem(m_OptionContents[1], EditorSplineUtility.DefaultTangentMode == TangentMode.AutoSmooth,
|
||||
() => SetTangentModeIfNeeded(TangentMode.AutoSmooth));
|
||||
|
||||
menu.DropDown(worldBound);
|
||||
}
|
||||
|
||||
void SetTangentModeIfNeeded(TangentMode tangentMode)
|
||||
{
|
||||
if (EditorSplineUtility.DefaultTangentMode != tangentMode)
|
||||
{
|
||||
EditorSplineUtility.s_DefaultTangentMode.SetValue(tangentMode, true);
|
||||
RefreshElementContent();
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshElementContent()
|
||||
{
|
||||
var content = m_OptionContents[EditorSplineUtility.DefaultTangentMode == TangentMode.Linear ? 0 : 1];
|
||||
text = content.text;
|
||||
tooltip = content.tooltip;
|
||||
icon = content.image as Texture2D;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: facf982a205a4c93830bb62332a75ceb
|
||||
timeCreated: 1649792821
|
||||
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.UIElements;
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
class Float3PropertyField<T> : Vector3Field
|
||||
where T : ISelectableElement
|
||||
{
|
||||
static readonly List<float3> s_Float3Buffer = new List<float3>();
|
||||
static readonly SplineGUIUtility.EqualityComparer<float3> s_ComparerX = (a, b) => a.x.Equals(b.x);
|
||||
static readonly SplineGUIUtility.EqualityComparer<float3> s_ComparerY = (a, b) => a.y.Equals(b.y);
|
||||
static readonly SplineGUIUtility.EqualityComparer<float3> s_ComparerZ = (a, b) => a.z.Equals(b.z);
|
||||
|
||||
readonly FloatField m_X;
|
||||
readonly FloatField m_Y;
|
||||
readonly FloatField m_Z;
|
||||
|
||||
readonly Func<T, float3> m_Get;
|
||||
readonly Action<T, float3> m_Set;
|
||||
|
||||
IReadOnlyList<T> m_Elements = new List<T>(0);
|
||||
|
||||
public event Action changed;
|
||||
|
||||
public Float3PropertyField(string label, Func<T, float3> get, Action<T, float3> set) : base(label)
|
||||
{
|
||||
m_Get = get;
|
||||
m_Set = set;
|
||||
|
||||
m_X = this.Q<FloatField>("unity-x-input");
|
||||
m_Y = this.Q<FloatField>("unity-y-input");
|
||||
m_Z = this.Q<FloatField>("unity-z-input");
|
||||
|
||||
m_X.RegisterValueChangedCallback(ApplyX);
|
||||
m_Y.RegisterValueChangedCallback(ApplyY);
|
||||
m_Z.RegisterValueChangedCallback(ApplyZ);
|
||||
}
|
||||
|
||||
public void Update(IReadOnlyList<T> elements)
|
||||
{
|
||||
m_Elements = elements;
|
||||
|
||||
s_Float3Buffer.Clear();
|
||||
for (int i = 0; i < elements.Count; ++i)
|
||||
s_Float3Buffer.Add(m_Get.Invoke(elements[i]));
|
||||
|
||||
var value = s_Float3Buffer.Count > 0 ? s_Float3Buffer[0] : 0;
|
||||
m_X.showMixedValue = SplineGUIUtility.HasMultipleValues(s_Float3Buffer, s_ComparerX);
|
||||
if (!m_X.showMixedValue)
|
||||
m_X.SetValueWithoutNotify(value[0]);
|
||||
|
||||
m_Y.showMixedValue = SplineGUIUtility.HasMultipleValues(s_Float3Buffer, s_ComparerY);
|
||||
if (!m_Y.showMixedValue)
|
||||
m_Y.SetValueWithoutNotify(value[1]);
|
||||
|
||||
m_Z.showMixedValue = SplineGUIUtility.HasMultipleValues(s_Float3Buffer, s_ComparerZ);
|
||||
if (!m_Z.showMixedValue)
|
||||
m_Z.SetValueWithoutNotify(value[2]);
|
||||
}
|
||||
|
||||
void ApplyX(ChangeEvent<float> evt)
|
||||
{
|
||||
EditorSplineUtility.RecordObjects(m_Elements, SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
|
||||
ElementInspector.ignoreKnotCallbacks = true;
|
||||
for (int i = 0; i < m_Elements.Count; ++i)
|
||||
{
|
||||
var value = m_Get.Invoke(m_Elements[i]);
|
||||
value.x = evt.newValue;
|
||||
m_Set.Invoke(m_Elements[i], value);
|
||||
}
|
||||
|
||||
m_X.showMixedValue = false;
|
||||
m_X.SetValueWithoutNotify(evt.newValue);
|
||||
changed?.Invoke();
|
||||
ElementInspector.ignoreKnotCallbacks = false;
|
||||
}
|
||||
|
||||
void ApplyY(ChangeEvent<float> evt)
|
||||
{
|
||||
EditorSplineUtility.RecordObjects(m_Elements, SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
|
||||
ElementInspector.ignoreKnotCallbacks = true;
|
||||
for (int i = 0; i < m_Elements.Count; ++i)
|
||||
{
|
||||
var value = m_Get.Invoke(m_Elements[i]);
|
||||
value.y = evt.newValue;
|
||||
m_Set.Invoke(m_Elements[i], value);
|
||||
}
|
||||
m_Y.showMixedValue = false;
|
||||
m_Y.SetValueWithoutNotify(evt.newValue);
|
||||
changed?.Invoke();
|
||||
ElementInspector.ignoreKnotCallbacks = false;
|
||||
}
|
||||
|
||||
void ApplyZ(ChangeEvent<float> evt)
|
||||
{
|
||||
EditorSplineUtility.RecordObjects(m_Elements, SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
|
||||
ElementInspector.ignoreKnotCallbacks = true;
|
||||
for (int i = 0; i < m_Elements.Count; ++i)
|
||||
{
|
||||
var value = m_Get.Invoke(m_Elements[i]);
|
||||
value.z = evt.newValue;
|
||||
m_Set.Invoke(m_Elements[i], value);
|
||||
}
|
||||
m_Z.showMixedValue = false;
|
||||
m_Z.SetValueWithoutNotify(evt.newValue);
|
||||
changed?.Invoke();
|
||||
ElementInspector.ignoreKnotCallbacks = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae3c8f89802f5a242813000e2c3b55a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.Toolbars;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[EditorToolbarElement("Spline Tool Settings/Handle Rotation")]
|
||||
class HandleRotationDropdown : EditorToolbarDropdown
|
||||
{
|
||||
const string k_ParentRotationIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/ToolHandleParent.png";
|
||||
const string k_ElementRotationIconPath = "Packages/com.unity.splines/Editor/Resources/Icons/ToolHandleElement.png";
|
||||
|
||||
readonly List<GUIContent> m_OptionContents = new List<GUIContent>();
|
||||
|
||||
public HandleRotationDropdown()
|
||||
{
|
||||
name = "Handle Rotation";
|
||||
|
||||
var content = EditorGUIUtility.TrTextContent("Local",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in the active object's rotation.",
|
||||
"ToolHandleLocal");
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Global",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in global rotation.",
|
||||
"ToolHandleGlobal");
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Parent",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in active element's parent's rotation.",
|
||||
k_ParentRotationIconPath);
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
content = EditorGUIUtility.TrTextContent("Element",
|
||||
"Toggle Tool Handle Rotation\n\nTool handles are in active element's rotation.",
|
||||
k_ElementRotationIconPath);
|
||||
m_OptionContents.Add(content);
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>(AttachedToPanel);
|
||||
RegisterCallback<DetachFromPanelEvent>(DetachedFromPanel);
|
||||
|
||||
clicked += OpenContextMenu;
|
||||
|
||||
RefreshElementContent();
|
||||
}
|
||||
|
||||
void OpenContextMenu()
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Global], SplineTool.handleOrientation == HandleOrientation.Global,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Global));
|
||||
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Local], SplineTool.handleOrientation == HandleOrientation.Local,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Local));
|
||||
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Parent], SplineTool.handleOrientation == HandleOrientation.Parent,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Parent));
|
||||
|
||||
menu.AddItem(m_OptionContents[(int)HandleOrientation.Element], SplineTool.handleOrientation == HandleOrientation.Element,
|
||||
() => SetHandleOrientationIfNeeded(HandleOrientation.Element));
|
||||
|
||||
menu.DropDown(worldBound);
|
||||
}
|
||||
|
||||
void SetHandleOrientationIfNeeded(HandleOrientation handleOrientation)
|
||||
{
|
||||
if (SplineTool.handleOrientation != handleOrientation)
|
||||
{
|
||||
SplineTool.handleOrientation = handleOrientation;
|
||||
RefreshElementContent();
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshElementContent()
|
||||
{
|
||||
var content = m_OptionContents[(int)SplineTool.handleOrientation];
|
||||
|
||||
text = content.text;
|
||||
tooltip = content.tooltip;
|
||||
icon = content.image as Texture2D;
|
||||
}
|
||||
|
||||
void AttachedToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
SplineTool.handleOrientationChanged += RefreshElementContent;
|
||||
}
|
||||
|
||||
void DetachedFromPanel(DetachFromPanelEvent evt)
|
||||
{
|
||||
SplineTool.handleOrientationChanged -= RefreshElementContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29ae16933bbb94210b13408d3f86a072
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class Separator : VisualElement
|
||||
{
|
||||
public Separator()
|
||||
{
|
||||
AddToClassList("unity-separator");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be6977c835160b643b208844b583a89c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.Toolbars;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[EditorToolbarElement("Spline Tool Settings/Handle Visuals")]
|
||||
sealed class SplineHandleSettingsDropdown : EditorToolbarDropdown
|
||||
{
|
||||
public SplineHandleSettingsDropdown()
|
||||
{
|
||||
var content = EditorGUIUtility.TrTextContent("Visuals", "Visual settings for handles");
|
||||
|
||||
text = content.text;
|
||||
tooltip = content.tooltip;
|
||||
icon = (Texture2D)content.image;
|
||||
|
||||
clicked += OnClick;
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>(AttachToPanel);
|
||||
}
|
||||
|
||||
void OnClick()
|
||||
{
|
||||
SplineHandleSettingsWindow.Show(worldBound);
|
||||
}
|
||||
|
||||
void AttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
var toolType = ToolManager.activeToolType;
|
||||
SetEnabled(toolType != typeof(KnotPlacementTool));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9935a56c1434b7845abe3f2b8f6600f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class SplineHandleSettingsWindow : EditorWindow
|
||||
{
|
||||
const float k_BorderWidth = 1;
|
||||
|
||||
Toggle m_FlowDirection;
|
||||
Toggle m_AllTangents;
|
||||
Toggle m_KnotIndices;
|
||||
Toggle m_SplineMesh;
|
||||
|
||||
public static void Show(Rect buttonRect)
|
||||
{
|
||||
var window = CreateInstance<SplineHandleSettingsWindow>();
|
||||
window.hideFlags = HideFlags.DontSave;
|
||||
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
var popupWidth = 150;
|
||||
#else
|
||||
var popupWidth = 180;
|
||||
#endif
|
||||
window.ShowAsDropDown(GUIUtility.GUIToScreenRect(buttonRect), new Vector2(popupWidth, 80));
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Color borderColor = EditorGUIUtility.isProSkin ? new Color(0.44f, 0.44f, 0.44f, 1f) : new Color(0.51f, 0.51f, 0.51f);
|
||||
|
||||
rootVisualElement.style.borderLeftWidth = k_BorderWidth;
|
||||
rootVisualElement.style.borderTopWidth = k_BorderWidth;
|
||||
rootVisualElement.style.borderRightWidth = k_BorderWidth;
|
||||
rootVisualElement.style.borderBottomWidth = k_BorderWidth;
|
||||
rootVisualElement.style.borderLeftColor = borderColor;
|
||||
rootVisualElement.style.borderTopColor = borderColor;
|
||||
rootVisualElement.style.borderRightColor = borderColor;
|
||||
rootVisualElement.style.borderBottomColor = borderColor;
|
||||
|
||||
rootVisualElement.Add(m_FlowDirection = new Toggle(L10n.Tr("Flow Direction")));
|
||||
m_FlowDirection.style.flexDirection = FlexDirection.RowReverse;
|
||||
|
||||
rootVisualElement.Add(m_AllTangents = new Toggle(L10n.Tr("All Tangents")));
|
||||
m_AllTangents.style.flexDirection = FlexDirection.RowReverse;
|
||||
|
||||
rootVisualElement.Add(m_KnotIndices = new Toggle(L10n.Tr("Knot Indices")));
|
||||
m_KnotIndices.style.flexDirection = FlexDirection.RowReverse;
|
||||
|
||||
rootVisualElement.Add(m_SplineMesh = new Toggle(L10n.Tr("Show Mesh")));
|
||||
m_SplineMesh.style.flexDirection = FlexDirection.RowReverse;
|
||||
|
||||
|
||||
m_FlowDirection.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
SplineHandleSettings.FlowDirectionEnabled = evt.newValue;
|
||||
SceneView.RepaintAll();
|
||||
});
|
||||
m_AllTangents.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
SplineHandleSettings.ShowAllTangents = evt.newValue;
|
||||
SceneView.RepaintAll();
|
||||
});
|
||||
m_KnotIndices.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
SplineHandleSettings.ShowKnotIndices = evt.newValue;
|
||||
SceneView.RepaintAll();
|
||||
});
|
||||
m_SplineMesh.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
SplineHandleSettings.ShowMesh = evt.newValue;
|
||||
SceneView.RepaintAll();
|
||||
});
|
||||
|
||||
UpdateValues();
|
||||
}
|
||||
|
||||
|
||||
void UpdateValues()
|
||||
{
|
||||
m_FlowDirection.SetValueWithoutNotify(SplineHandleSettings.FlowDirectionEnabled);
|
||||
m_AllTangents.SetValueWithoutNotify(SplineHandleSettings.ShowAllTangents);
|
||||
m_KnotIndices.SetValueWithoutNotify(SplineHandleSettings.ShowKnotIndices);
|
||||
m_SplineMesh.SetValueWithoutNotify(SplineHandleSettings.ShowMesh);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8c433db06b595546b18ebbe3e27c83f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.Overlays;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
[Icon("UnityEditor.InspectorWindow")]
|
||||
[Overlay(typeof(SceneView), "unity-spline-inspector", "Element Inspector", "SplineInspector")]
|
||||
sealed class SplineInspectorOverlay : Overlay, ITransientOverlay
|
||||
{
|
||||
internal static readonly string SplineChangeUndoMessage = L10n.Tr("Apply Changes to Spline");
|
||||
public static void ForceUpdate()
|
||||
{
|
||||
s_ForceUpdateRequested?.Invoke();
|
||||
}
|
||||
|
||||
static event Action s_ForceUpdateRequested;
|
||||
static bool s_FirstUpdateSinceDomainReload = true;
|
||||
static IReadOnlyList<SplineInfo> m_SelectedSplines;
|
||||
internal static void SetSelectedSplines(IReadOnlyList<SplineInfo> splines)
|
||||
{
|
||||
m_SelectedSplines = splines;
|
||||
if (s_FirstUpdateSinceDomainReload)
|
||||
{
|
||||
s_FirstUpdateSinceDomainReload = false;
|
||||
ForceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public bool visible => ToolManager.activeContextType == typeof(SplineToolContext) && ToolManager.activeToolType != typeof(KnotPlacementTool);
|
||||
|
||||
ElementInspector m_ElementInspector;
|
||||
|
||||
public override VisualElement CreatePanelContent()
|
||||
{
|
||||
VisualElement root = new VisualElement();
|
||||
root.Add(m_ElementInspector = new ElementInspector());
|
||||
|
||||
UpdateInspector();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public override void OnCreated()
|
||||
{
|
||||
displayedChanged += OnDisplayedChange;
|
||||
SplineSelection.changed += UpdateInspector;
|
||||
s_ForceUpdateRequested += UpdateInspector;
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
}
|
||||
|
||||
public override void OnWillBeDestroyed()
|
||||
{
|
||||
displayedChanged -= OnDisplayedChange;
|
||||
SplineSelection.changed -= UpdateInspector;
|
||||
s_ForceUpdateRequested -= UpdateInspector;
|
||||
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
|
||||
}
|
||||
|
||||
void OnDisplayedChange(bool displayed)
|
||||
{
|
||||
UpdateInspector();
|
||||
}
|
||||
|
||||
void UpdateInspector()
|
||||
{
|
||||
if (m_SelectedSplines == null)
|
||||
return;
|
||||
|
||||
m_ElementInspector?.UpdateSelection(m_SelectedSplines);
|
||||
}
|
||||
|
||||
void OnUndoRedoPerformed()
|
||||
{
|
||||
ForceUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5737da4b5db2be640adcc9bb3360aa91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Splines
|
||||
{
|
||||
sealed class TangentDrawer : ElementDrawer<SelectableTangent>
|
||||
{
|
||||
const string k_TangentDrawerStyle = "tangent-drawer";
|
||||
|
||||
static readonly List<float> s_LengthBuffer = new List<float>(0);
|
||||
static readonly SplineGUIUtility.EqualityComparer<float> s_MagnitudeComparer = (a, b) => a.Equals(b);
|
||||
|
||||
readonly TangentModePropertyField<SelectableTangent> m_Mode;
|
||||
readonly BezierTangentPropertyField<SelectableTangent> m_BezierMode;
|
||||
|
||||
FloatField m_Magnitude;
|
||||
Float3PropertyField<SelectableTangent> m_Direction;
|
||||
|
||||
public TangentDrawer()
|
||||
{
|
||||
AddToClassList(k_TangentDrawerStyle);
|
||||
|
||||
Add(m_Mode = new TangentModePropertyField<SelectableTangent>());
|
||||
m_Mode.changed += () =>
|
||||
{
|
||||
m_BezierMode.Update(targets);
|
||||
EnableElements();
|
||||
};
|
||||
Add(m_BezierMode = new BezierTangentPropertyField<SelectableTangent>());
|
||||
m_BezierMode.changed += () =>
|
||||
{
|
||||
m_Mode.Update(targets);
|
||||
EnableElements();
|
||||
};
|
||||
|
||||
CreateTangentFields();
|
||||
|
||||
m_Magnitude.RegisterValueChangedCallback((evt) =>
|
||||
{
|
||||
var value = evt.newValue;
|
||||
if (evt.newValue < 0f)
|
||||
{
|
||||
m_Magnitude.SetValueWithoutNotify(0f);
|
||||
value = 0f;
|
||||
}
|
||||
|
||||
Undo.RecordObject(target.SplineInfo.Object, SplineInspectorOverlay.SplineChangeUndoMessage);
|
||||
UpdateTangentMagnitude(value);
|
||||
var tangent = target;
|
||||
m_Direction.SetValueWithoutNotify(tangent.LocalPosition);
|
||||
});
|
||||
}
|
||||
|
||||
public override string GetLabelForTargets()
|
||||
{
|
||||
if (targets.Count > 1)
|
||||
return $"<b>({targets.Count}) Tangents</b> selected";
|
||||
|
||||
var inOutLabel = target.TangentIndex == 0 ? "In" : "Out";
|
||||
return $"Tangent <b>{inOutLabel}</b> selected (<b>Knot {target.KnotIndex}</b>, <b>Spline {target.SplineInfo.Index}</b>)";
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
m_Mode.Update(targets);
|
||||
m_BezierMode.Update(targets);
|
||||
|
||||
UpdateMagnitudeField(targets);
|
||||
m_Direction.Update(targets);
|
||||
|
||||
EnableElements();
|
||||
}
|
||||
|
||||
void CreateTangentFields()
|
||||
{
|
||||
Add(m_Magnitude = new FloatField(L10n.Tr("Length"), 6));
|
||||
|
||||
Add(m_Direction = new Float3PropertyField<SelectableTangent>(L10n.Tr("Direction"),
|
||||
(tangent) => tangent.LocalDirection,
|
||||
(tangent, value) => tangent.LocalDirection = value)
|
||||
{ name = "direction" });
|
||||
m_Direction.changed += () => { UpdateMagnitudeField(targets); };
|
||||
}
|
||||
|
||||
void UpdateMagnitudeField(IReadOnlyList<SelectableTangent> tangents)
|
||||
{
|
||||
s_LengthBuffer.Clear();
|
||||
for (int i = 0; i < tangents.Count; ++i)
|
||||
s_LengthBuffer.Add(math.length(tangents[i].LocalPosition));
|
||||
|
||||
m_Magnitude.showMixedValue = SplineGUIUtility.HasMultipleValues(s_LengthBuffer, s_MagnitudeComparer);
|
||||
if (!m_Magnitude.showMixedValue)
|
||||
m_Magnitude.SetValueWithoutNotify(s_LengthBuffer[0]);
|
||||
}
|
||||
|
||||
void UpdateTangentMagnitude(float value)
|
||||
{
|
||||
ElementInspector.ignoreKnotCallbacks = true;
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
var direction = new float3(0, 0, 1);
|
||||
|
||||
var tangent = targets[i];
|
||||
if (math.length(tangent.LocalPosition) > 0)
|
||||
direction = math.normalize(tangent.LocalPosition);
|
||||
|
||||
tangent.LocalPosition = value * direction;
|
||||
}
|
||||
ElementInspector.ignoreKnotCallbacks = false;
|
||||
}
|
||||
|
||||
void EnableElements()
|
||||
{
|
||||
bool tangentsModifiable = true;
|
||||
bool tangentsBroken = true;
|
||||
for (int i = 0; i < targets.Count; ++i)
|
||||
{
|
||||
var mode = targets[i].Owner.Mode;
|
||||
tangentsModifiable &= SplineUtility.AreTangentsModifiable(mode);
|
||||
tangentsBroken &= mode == TangentMode.Broken;
|
||||
}
|
||||
|
||||
m_Direction.SetEnabled(tangentsModifiable && tangentsBroken);
|
||||
m_Magnitude.SetEnabled(tangentsModifiable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d01d6b9007f24984692322edf5415826
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user