using System; using System.Collections.Generic; using UnityEditor.EditorTools; using UnityEngine; using UnityEngine.Splines; namespace UnityEditor.Splines { [Flags] enum SplineHandlesOptions { ShowTangents = 1 << 0, SelectableKnots = 1 << 1, SelectableTangents = 1 << 2, KnotInsert = 1 << 3, SelectableElements = SelectableKnots | SelectableTangents, None = 0, ManipulationDefault = ShowTangents | SelectableElements } /// /// Defines a tool context for editing splines. When authoring tools for splines, pass the SplineToolContext type /// to the EditorToolAttribute.editorToolContext parameter to register as a spline tool. /// #if UNITY_2021_2_OR_NEWER [EditorToolContext("Spline", typeof(ISplineProvider)), Icon(k_IconPath)] #else [EditorToolContext("Spline", typeof(ISplineProvider))] #endif public sealed class SplineToolContext : EditorToolContext { const string k_IconPath = "Packages/com.unity.splines/Editor/Resources/Icons/SplineContext.png"; static SplineHandlesOptions s_SplineHandlesOptions = SplineHandlesOptions.None; static bool s_UseCustomSplineHandles = false; static bool s_IsSplineTool = true; readonly SplineElementRectSelector m_RectSelector = new SplineElementRectSelector(); readonly List m_Splines = new List(); readonly List m_KnotBuffer = new List(); bool m_WasActiveAfterDeserialize; internal static void SetHandlesOptions(SplineHandlesOptions options) { s_SplineHandlesOptions = options; } internal static void UseCustomSplineHandles(bool useCustomSplineHandle) { s_UseCustomSplineHandles = useCustomSplineHandle; } /// /// Returns the matching EditorTool type for the specified Tool given the context. /// /// The Tool to resolve to an EditorTool type. /// An EditorTool type for the requested Tool. protected override Type GetEditorToolType(Tool tool) { if (tool == Tool.Move) return typeof(SplineMoveTool); if (tool == Tool.Rotate) return typeof(SplineRotateTool); if (tool == Tool.Scale) return typeof(SplineScaleTool); return null; } /// /// Invoked for each window where this context is active. The spline context uses this method to implement /// common functionality for working with splines, ex gizmo drawing and selection. /// /// public override void OnToolGUI(EditorWindow window) { if(!s_IsSplineTool) return; m_RectSelector.OnGUI(m_Splines); if(!s_UseCustomSplineHandles) SplineHandles.DrawSplineHandles(m_Splines, s_SplineHandlesOptions); HandleSelectionFraming(); HandleSelectAll(); HandleDeleteSelectedKnots(); } void OnEnable() { AssemblyReloadEvents.afterAssemblyReload += OnAfterDomainReload; } /// /// Invoked after this EditorToolContext becomes the active tool context. /// public override void OnActivated() { // Sync handleOrientation to Tools.pivotRotation only if we're switching from a different context. // This ensures that Parent/Element handleOrientation is retained after domain reload. if(!m_WasActiveAfterDeserialize) SplineTool.handleOrientation = (HandleOrientation)Tools.pivotRotation; else m_WasActiveAfterDeserialize = false; OnSelectionChanged(); ToolManager.activeToolChanged += OnActiveToolChanged; Selection.selectionChanged += OnSelectionChanged; Spline.afterSplineWasModified += OnSplineWasModified; Undo.undoRedoPerformed += UndoRedoPerformed; SplineConversionUtility.splinesUpdated += SceneView.RepaintAll; SplineConversionUtility.UpdateEditableSplinesForTargets(targets); } /// /// Invoked before this EditorToolContext stops being the active tool context. /// public override void OnWillBeDeactivated() { ToolManager.activeToolChanged -= OnActiveToolChanged; Selection.selectionChanged -= OnSelectionChanged; Spline.afterSplineWasModified -= OnSplineWasModified; Undo.undoRedoPerformed -= UndoRedoPerformed; SplineConversionUtility.splinesUpdated -= SceneView.RepaintAll; EditableSplineManager.FreeEntireCache(); SplineSelection.ClearNoUndo(false); } void OnActiveToolChanged() { if(ToolManager.activeToolType == typeof(KnotPlacementTool)) SplineSelection.Clear(); s_IsSplineTool = typeof(SplineTool).IsAssignableFrom(ToolManager.activeToolType); } void OnSplineWasModified(Spline spline) => UpdateSelection(); void OnSelectionChanged() => UpdateSelection(); void UndoRedoPerformed() => UpdateSelection(); void UpdateSelection() { EditableSplineUtility.GetSelectedSplines(targets, m_Splines); EditableSplineManager.UpdateSelection(targets); SplineSelection.UpdateObjectSelection(targets); SceneView.RepaintAll(); } void HandleDeleteSelectedKnots() { Event evt = Event.current; if (evt.type == EventType.KeyDown && evt.keyCode == KeyCode.Delete) { SplineSelection.GetSelectedKnots(m_KnotBuffer); //Sort knots index so removing them doesn't cause the rest of the indices to be invalid m_KnotBuffer.Sort((a, b) => a.index.CompareTo(b.index)); for (int i = m_KnotBuffer.Count - 1; i >= 0; --i) { EditableKnot knot = m_KnotBuffer[i]; knot.spline.RemoveKnotAt(knot.index); } evt.Use(); } } void HandleSelectionFraming() { if (TransformOperation.canManipulate) { Event evt = Event.current; if (evt.commandName.Equals("FrameSelected")) { var execute = evt.type == EventType.ExecuteCommand; if (evt.type == EventType.ValidateCommand || execute) { if (execute) { var selectionBounds = TransformOperation.GetSelectionBounds(false); selectionBounds.Encapsulate(TransformOperation.pivotPosition); var size = selectionBounds.size; if (selectionBounds.size.x < 1f) size.x = 1f; if (selectionBounds.size.y < 1f) size.y = 1f; if (selectionBounds.size.z < 1f) size.z = 1f; selectionBounds.size = size; SceneView.lastActiveSceneView.Frame(selectionBounds, false); } evt.Use(); } } } } void HandleSelectAll() { Event evt = Event.current; if (evt.commandName.Equals("SelectAll")) { var execute = evt.type == EventType.ExecuteCommand; if (evt.type == EventType.ValidateCommand || execute) { var allElements = new List(); foreach (var spline in m_Splines) { for (int knotIdx = 0; knotIdx < spline.knotCount; ++knotIdx) { var knot = spline.GetKnot(knotIdx); allElements.Add(knot); for (int tangentIdx = 0; tangentIdx < knot.tangentCount; ++tangentIdx) { var tangent = knot.GetTangent(tangentIdx); if (SplineSelectionUtility.IsSelectable(spline, knotIdx, tangent)) allElements.Add(tangent); } } } SplineSelection.Add(allElements); evt.Use(); } } } void OnAfterDomainReload() { m_WasActiveAfterDeserialize = ToolManager.activeContextType == typeof(SplineToolContext); AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload; } } }