WuhuIslandTesting/Library/PackageCache/com.unity.splines@1.0.1/Editor/Tools/SplineTool.cs
2025-01-07 02:06:59 +01:00

323 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.EditorTools;
using UnityEngine;
using UnityEditor.SettingsManagement;
using UnityEditor.ShortcutManagement;
using UnityEngine.Splines;
#if UNITY_2022_1_OR_NEWER
using UnityEditor.Overlays;
#else
using System.Reflection;
using UnityEditor.Toolbars;
using UnityEngine.UIElements;
#endif
namespace UnityEditor.Splines
{
/// <summary>
/// Describes how the handles are oriented.
/// </summary>
enum HandleOrientation
{
/// <summary>
/// Tool handles are in the active object's rotation.
/// </summary>
Local = 0,
/// <summary>
/// Tool handles are in global rotation.
/// </summary>
Global = 1,
/// <summary>
/// Tool handles are in active element's parent's rotation.
/// </summary>
Parent = 2,
/// <summary>
/// Tool handles are in active element's rotation.
/// </summary>
Element = 3
}
#if UNITY_2022_1_OR_NEWER
abstract class SplineToolSettings : UnityEditor.Editor, ICreateToolbar
#else
abstract class SplineToolSettings : UnityEditor.Editor
#endif
{
public virtual IEnumerable<string> toolbarElements
{
get
{
yield return "Tool Settings/Pivot Mode";
yield return "Spline Tool Settings/Handle Rotation";
}
}
#if !UNITY_2022_1_OR_NEWER
const string k_ElementClassName = "unity-editor-toolbar-element";
const string k_StyleSheetsPath = "StyleSheets/Toolbars/";
static VisualElement CreateToolbar()
{
var target = new VisualElement();
var path = k_StyleSheetsPath + "EditorToolbar";
var common = EditorGUIUtility.Load($"{path}Common.uss") as StyleSheet;
if (common != null)
target.styleSheets.Add(common);
var themeSpecificName = EditorGUIUtility.isProSkin ? "Dark" : "Light";
var themeSpecific = EditorGUIUtility.Load($"{path}{themeSpecificName}.uss") as StyleSheet;
if (themeSpecific != null)
target.styleSheets.Add(themeSpecific);
target.AddToClassList("unity-toolbar-overlay");
target.style.flexDirection = FlexDirection.Row;
return target;
}
public override VisualElement CreateInspectorGUI()
{
var root = CreateToolbar();
var elements = TypeCache.GetTypesWithAttribute(typeof(EditorToolbarElementAttribute));
foreach (var element in toolbarElements)
{
var type = elements.FirstOrDefault(x =>
{
var attrib = x.GetCustomAttribute<EditorToolbarElementAttribute>();
return attrib != null && attrib.id == element;
});
if (type != null)
{
try
{
const BindingFlags flags = BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.CreateInstance;
var ve = (VisualElement)Activator.CreateInstance(type, flags, null, null, null, null);
ve.AddToClassList(k_ElementClassName);
root.Add(ve);
}
catch (Exception e)
{
Debug.LogError($"Failed creating toolbar element from ID \"{element}\".\n{e}");
}
}
}
EditorToolbarUtility.SetupChildrenAsButtonStrip(root);
return root;
}
#endif
}
/// <summary>
/// Base class from which all Spline tools inherit.
/// Inherit SplineTool to author tools that behave like native spline tools. This class implements some common
/// functionality and shortcuts specific to spline authoring.
/// </summary>
abstract class SplineTool : EditorTool
{
internal virtual SplineHandlesOptions handlesOptions => SplineHandlesOptions.None;
static UserSetting<HandleOrientation> m_HandleOrientation = new UserSetting<HandleOrientation>(PathSettings.instance, "SplineTool.HandleOrientation", HandleOrientation.Global, SettingsScope.User);
public static HandleOrientation handleOrientation
{
get => m_HandleOrientation;
set
{
if (m_HandleOrientation != value)
{
m_HandleOrientation.SetValue(value, true);
if(m_HandleOrientation == HandleOrientation.Local || m_HandleOrientation == HandleOrientation.Global)
Tools.pivotRotation = (PivotRotation)m_HandleOrientation.value;
else // If setting HandleOrientation to something else, then set the PivotRotation to global, done for GridSnapping button activation
{
Tools.pivotRotationChanged -= OnPivotRotationChanged;
Tools.pivotRotation = PivotRotation.Local;
Tools.pivotRotationChanged += OnPivotRotationChanged;
}
handleOrientationChanged?.Invoke();
}
}
}
internal static event Action handleOrientationChanged;
// Workaround for lack of access to ShortcutContext. Use this to pass shortcut actions to tool instances.
protected static SplineTool m_ActiveTool;
/// <summary>
/// Invoked after this EditorTool becomes the active tool.
/// </summary>
public override void OnActivated()
{
SplineToolContext.SetHandlesOptions(handlesOptions);
SplineSelection.changed += OnSplineSelectionChanged;
Spline.afterSplineWasModified += AfterSplineWasModified;
Undo.undoRedoPerformed += UndoRedoPerformed;
Tools.pivotRotationChanged += OnPivotRotationChanged;
Tools.pivotModeChanged += OnPivotModeChanged;
TransformOperation.UpdateSelection(targets);
handleOrientationChanged += OnHandleOrientationChanged;
m_ActiveTool = this;
}
/// <summary>
/// Invoked before this EditorTool stops being the active tool.
/// </summary>
public override void OnWillBeDeactivated()
{
SplineToolContext.SetHandlesOptions(SplineHandlesOptions.None);
SplineSelection.changed -= OnSplineSelectionChanged;
Spline.afterSplineWasModified -= AfterSplineWasModified;
Undo.undoRedoPerformed -= UndoRedoPerformed;
Tools.pivotRotationChanged -= OnPivotRotationChanged;
Tools.pivotModeChanged -= OnPivotModeChanged;
handleOrientationChanged -= OnHandleOrientationChanged;
m_ActiveTool = null;
}
protected virtual void OnHandleOrientationChanged()
{
TransformOperation.UpdateHandleRotation();
}
static void OnPivotRotationChanged()
{
handleOrientation = (HandleOrientation)Tools.pivotRotation;
}
protected virtual void OnPivotModeChanged()
{
TransformOperation.UpdatePivotPosition();
TransformOperation.UpdateHandleRotation();
}
void AfterSplineWasModified(Spline spline) => UpdateSelection();
void UndoRedoPerformed() => UpdateSelection();
void OnSplineSelectionChanged()
{
TransformOperation.pivotFreeze = TransformOperation.PivotFreeze.None;
TransformOperation.UpdateHandleRotation();
TransformOperation.UpdatePivotPosition();
UpdateSelection();
}
void UpdateSelection()
{
TransformOperation.UpdateSelection(targets);
}
void CycleTangentMode()
{
var elementSelection = TransformOperation.elementSelection;
foreach (var element in elementSelection)
{
if (element is EditableTangent tangent)
{
//Do nothing on the tangent if the knot is also in the selection
if (elementSelection.Contains(tangent.owner))
continue;
var oppositeTangentSelected = false;
if (tangent.owner is BezierEditableKnot owner)
{
if(owner.TryGetOppositeTangent(tangent, out var oppositeTangent))
{
if (elementSelection.Contains(oppositeTangent))
oppositeTangentSelected = true;
}
if (!oppositeTangentSelected)
{
if (owner.mode == BezierEditableKnot.Mode.Broken)
{
// Mirror otherTangent against the active tangent prior to SetMode call.
// As SetMode always mirrors tangentOut against tangentIn, this prevents an active selection's
// tangentOut from shrinking or becoming zero tangent unexpectedly.
for (int i = 0; i < owner.tangentCount; ++i)
{
var otherTangent = owner.GetTangent(i);
if (otherTangent != tangent)
otherTangent.SetLocalPositionNoNotify(-tangent.localPosition);
}
owner.SetMode(BezierEditableKnot.Mode.Mirrored);
}
else if (owner.mode == BezierEditableKnot.Mode.Mirrored)
owner.SetMode(BezierEditableKnot.Mode.Continuous);
else if (owner.mode == BezierEditableKnot.Mode.Continuous)
owner.SetMode(BezierEditableKnot.Mode.Broken);
owner.TangentChanged(tangent, owner.mode);
TransformOperation.UpdateHandleRotation();
// Ensures the tangent mode indicators refresh
SceneView.RepaintAll();
}
}
}
}
}
/// <summary>
/// Get the currently selected active spline.
/// </summary>
/// <returns>The active spline.</returns>
protected virtual IEditableSpline GetActiveSpline()
{
IReadOnlyList<IEditableSpline> paths = EditableSplineUtility.GetSelectedSpline(target);
if (paths == null || paths.Count == 0)
return null;
return paths[0];
}
[Shortcut("Splines/Cycle Tangent Mode", typeof(SceneView), KeyCode.C)]
static void ShortcutCycleTangentMode(ShortcutArguments args)
{
if(m_ActiveTool != null)
m_ActiveTool.CycleTangentMode();
}
[Shortcut("Splines/Toggle Manipulation Space", typeof(SceneView), KeyCode.X)]
static void ShortcutCycleHandleOrientation(ShortcutArguments args)
{
/* We're doing a switch here (instead of handleOrientation+1 and wrapping) because HandleOrientation.Global/Local values map
to PivotRotation.Global/Local (as they should), but PivotRotation.Global = 1 when it's actually the first option and PivotRotation.Local = 0 when it's the second option. */
switch (handleOrientation)
{
case HandleOrientation.Element:
handleOrientation = HandleOrientation.Global;
break;
case HandleOrientation.Global:
handleOrientation = HandleOrientation.Local;
break;
case HandleOrientation.Local:
handleOrientation = HandleOrientation.Parent;
break;
case HandleOrientation.Parent:
handleOrientation = HandleOrientation.Element;
break;
default:
Debug.LogError($"{handleOrientation} handle orientation not supported!");
break;
}
}
}
}