using System; using System.Collections.Generic; using System.Linq; using UnityEditorInternal; using UnityEngine; namespace UnityEditor.Timeline { class ClipInspectorCurveEditor { CurveEditor m_CurveEditor; CurveWrapper[] m_CurveWrappers; const float k_HeaderHeight = 30; const float k_PresetHeight = 30; Action m_CurveUpdatedCallback; GUIContent m_TextContent = new GUIContent(); GUIStyle m_LabelStyle; GUIStyle m_LegendStyle; // Track time. controls the position of the track head public static readonly double kDisableTrackTime = double.NaN; double m_trackTime = kDisableTrackTime; public double trackTime { get { return m_trackTime; } set { m_trackTime = value; } } public string headerString { get; set; } public ClipInspectorCurveEditor() { var curveEditorSettings = new CurveEditorSettings { allowDeleteLastKeyInCurve = false, allowDraggingCurvesAndRegions = true, hTickLabelOffset = 0.1f, showAxisLabels = true, useFocusColors = false, wrapColor = new EditorGUIUtility.SkinnedColor(Color.black), hSlider = false, hRangeMin = 0.0f, vRangeMin = 0.0F, vRangeMax = 1.0f, hRangeMax = 1.0F, vSlider = false, hRangeLocked = false, vRangeLocked = false, undoRedoSelection = true, hTickStyle = new TickStyle { tickColor = new EditorGUIUtility.SkinnedColor(new Color(0.0f, 0.0f, 0.0f, 0.2f)), distLabel = 30, stubs = false, centerLabel = true }, vTickStyle = new TickStyle { tickColor = new EditorGUIUtility.SkinnedColor(new Color(1.0f, 0.0f, 0.0f, 0.2f)), distLabel = 20, stubs = false, centerLabel = true } }; m_CurveEditor = new CurveEditor(new Rect(0, 0, 1000, 100), new CurveWrapper[0], true) { settings = curveEditorSettings, ignoreScrollWheelUntilClicked = true }; } internal bool InitStyles() { if (EditorStyles.s_Current == null) return false; if (m_LabelStyle == null) { m_LabelStyle = new GUIStyle(EditorStyles.whiteLargeLabel); m_LegendStyle = new GUIStyle(EditorStyles.miniBoldLabel); m_LabelStyle.alignment = TextAnchor.MiddleCenter; m_LegendStyle.alignment = TextAnchor.MiddleCenter; } return true; } internal void OnGUI(Rect clientRect, CurvePresetLibrary presets) { const float presetPad = 30.0f; if (!InitStyles()) return; if (m_CurveWrappers == null || m_CurveWrappers.Length == 0) return; // regions var headerRect = new Rect(clientRect.x, clientRect.y, clientRect.width, k_HeaderHeight); var curveRect = new Rect(clientRect.x, clientRect.y + headerRect.height, clientRect.width, clientRect.height - k_HeaderHeight - k_PresetHeight); var presetRect = new Rect(clientRect.x + presetPad, clientRect.y + curveRect.height + k_HeaderHeight, clientRect.width - presetPad, k_PresetHeight); GUI.Box(headerRect, headerString, m_LabelStyle); //Case 1201474 : Force to update only when Repaint event is called as the new rect provided on other event create a wrong curve editor computation. if (Event.current.type == EventType.Repaint) { m_CurveEditor.rect = curveRect; m_CurveEditor.shownAreaInsideMargins = new Rect(0, 0, 1, 1); } m_CurveEditor.animationCurves = m_CurveWrappers; UpdateSelectionColors(); DrawTrackHead(curveRect); EditorGUI.BeginChangeCheck(); m_CurveEditor.OnGUI(); DrawPresets(presetRect, presets); bool hasChanged = EditorGUI.EndChangeCheck(); if (presets == null) DrawLegend(presetRect); if (hasChanged) ProcessUpdates(); ConsumeMouseEvents(clientRect); } static void ConsumeMouseEvents(Rect rect) { var isMouseEvent = Event.current.type == EventType.MouseUp || Event.current.type == EventType.MouseDown; if (isMouseEvent && rect.Contains(Event.current.mousePosition)) Event.current.Use(); } void DrawPresets(Rect position, PresetLibrary curveLibrary) { if (curveLibrary == null || curveLibrary.Count() == 0) return; const int maxNumPresets = 9; int numPresets = curveLibrary.Count(); int showNumPresets = Mathf.Min(numPresets, maxNumPresets); const float swatchWidth = 30; const float swatchHeight = 15; const float spaceBetweenSwatches = 10; float presetButtonsWidth = showNumPresets * swatchWidth + (showNumPresets - 1) * spaceBetweenSwatches; float flexWidth = (position.width - presetButtonsWidth) * 0.5f; // Preset swatch area float curY = (position.height - swatchHeight) * 0.5f; float curX = 3.0f; if (flexWidth > 0) curX = flexWidth; GUI.BeginGroup(position); for (int i = 0; i < showNumPresets; i++) { if (i > 0) curX += spaceBetweenSwatches; var swatchRect = new Rect(curX, curY, swatchWidth, swatchHeight); m_TextContent.tooltip = curveLibrary.GetName(i); if (GUI.Button(swatchRect, m_TextContent, GUIStyle.none)) { // if there is only 1, no need to specify IEnumerable wrappers = m_CurveWrappers; if (m_CurveWrappers.Length > 1) wrappers = m_CurveWrappers.Where(x => x.selected == CurveWrapper.SelectionMode.Selected); foreach (var wrapper in wrappers) { var presetCurve = (AnimationCurve)curveLibrary.GetPreset(i); wrapper.curve.keys = (Keyframe[])presetCurve.keys.Clone(); wrapper.changed = true; } // case 1259902 - flushes internal selection caches preventing index out of range exceptions m_CurveEditor.SelectNone(); foreach (var wrapper in wrappers) wrapper.selected = CurveWrapper.SelectionMode.Selected; } if (Event.current.type == EventType.Repaint) curveLibrary.Draw(swatchRect, i); curX += swatchWidth; } GUI.EndGroup(); } // draw a line representing where in the current clip we are void DrawTrackHead(Rect clientRect) { DirectorStyles styles = TimelineWindow.styles; if (styles == null) return; if (!double.IsNaN(m_trackTime)) { float x = m_CurveEditor.TimeToPixel((float)m_trackTime, clientRect); x = Mathf.Clamp(x, clientRect.xMin, clientRect.xMax); var p1 = new Vector2(x, clientRect.yMin); var p2 = new Vector2(x, clientRect.yMax); Graphics.DrawLine(p1, p2, DirectorStyles.Instance.customSkin.colorPlayhead); } } // Draws a legend for the displayed curves void DrawLegend(Rect r) { if (m_CurveWrappers == null || m_CurveWrappers.Length == 0) return; Color c = GUI.color; float boxWidth = r.width / m_CurveWrappers.Length; for (int i = 0; i < m_CurveWrappers.Length; i++) { CurveWrapper cw = m_CurveWrappers[i]; if (cw != null) { var pos = new Rect(r.x + i * boxWidth, r.y, boxWidth, r.height); var textColor = cw.color; textColor.a = 1; GUI.color = textColor; string name = LabelName(cw.binding.propertyName); EditorGUI.LabelField(pos, name, m_LegendStyle); } } GUI.color = c; } // Helper for making label name appropriately small static char[] s_LabelMarkers = { '_' }; static string LabelName(string propertyName) { propertyName = AnimationWindowUtility.GetPropertyDisplayName(propertyName); int index = propertyName.LastIndexOfAny(s_LabelMarkers); if (index >= 0) propertyName = propertyName.Substring(index); return propertyName; } public void SetCurve(AnimationCurve curve) { if (m_CurveWrappers == null || m_CurveWrappers.Length != 1) { m_CurveWrappers = new CurveWrapper[1]; var cw = new CurveWrapper { renderer = new NormalCurveRenderer(curve), readOnly = false, color = EditorGUI.kCurveColor, id = 0xFEED, hidden = false, regionId = -1 }; cw.renderer.SetWrap(WrapMode.Clamp, WrapMode.Clamp); cw.renderer.SetCustomRange(0, 1); m_CurveWrappers[0] = cw; UpdateSelectionColors(); m_CurveEditor.animationCurves = m_CurveWrappers; } else { m_CurveWrappers[0].renderer = new NormalCurveRenderer(curve); } } internal void SetUpdateCurveCallback(Action callback) { m_CurveUpdatedCallback = callback; } void ProcessUpdates() { foreach (var cw in m_CurveWrappers) { if (cw.changed) { cw.changed = false; if (m_CurveUpdatedCallback != null) m_CurveUpdatedCallback(cw.curve, cw.binding); } } } public void SetSelected(AnimationCurve curve) { m_CurveEditor.SelectNone(); if (m_CurveWrappers != null && m_CurveWrappers.Length > 0) { if (m_CurveWrappers[0].renderer.GetCurve() == curve) { m_CurveWrappers[0].selected = CurveWrapper.SelectionMode.Selected; m_CurveEditor.AddSelection(new CurveSelection(m_CurveWrappers[0].id, 0)); } } UpdateSelectionColors(); } void UpdateSelectionColors() { if (m_CurveWrappers == null) return; // manually manage selection colors foreach (var cw in m_CurveWrappers) { Color c = cw.color; if (cw.readOnly) c.a = 0.75f; else if (cw.selected != CurveWrapper.SelectionMode.None) c.a = 1.0f; else c.a = 0.5f; cw.color = c; } } public static void CurveField(GUIContent title, SerializedProperty property, Action onClick) { Rect controlRect = EditorGUILayout.GetControlRect(GUILayout.MinWidth(20)); EditorGUI.BeginProperty(controlRect, title, property); DrawCurve(controlRect, property, onClick, EditorGUI.kCurveColor, EditorGUI.kCurveBGColor); EditorGUI.EndProperty(); } static Rect DrawCurve(Rect controlRect, SerializedProperty property, Action onClick, Color fgColor, Color bgColor) { if (GUI.Button(controlRect, GUIContent.none)) { if (onClick != null) onClick(property); } EditorGUIUtility.DrawCurveSwatch(controlRect, null, property, fgColor, bgColor); return controlRect; } } }