Ejemplo n.º 1
0
        ///Refresh curves of target animatable
        static void RefreshCurvesOf(IAnimatableData animatable)
        {
            CurveRenderer curveRenderer = null;

            if (cache.TryGetValue(animatable, out curveRenderer))
            {
                curveRenderer.RefreshCurves();
                return;
            }

            if (animatable is AnimationDataCollection)
            {
                var data = (AnimationDataCollection)animatable;
                if (data.animatedParameters != null)
                {
                    foreach (var animParam in data.animatedParameters)
                    {
                        if (cache.TryGetValue(animParam, out curveRenderer))
                        {
                            curveRenderer.RefreshCurves();
                        }
                    }
                }
            }
        }
Ejemplo n.º 2
0
 ///Refresh animation editors (dopesheet, curves) of targer animatable
 public static void RefreshAllAnimationEditorsOf(IAnimatableData animatable)
 {
     if (onRefreshAllAnimationEditors != null)
     {
         onRefreshAllAnimationEditors(animatable);
     }
 }
Ejemplo n.º 3
0
 public DopeSheetRenderer(IAnimatableData animatable)
 {
     this.animatable = animatable;
     this.allCurves  = animatable.GetCurves();
     RefreshDopeKeys(animatable);
     Undo.undoRedoPerformed += () => { ResetInteraction(); refreshDopeKeys = true; };
 }
Ejemplo n.º 4
0
 public CurveRenderer(IAnimatableData animatable, IKeyable keyable, Rect posRect)
 {
     this.animatable         = animatable;
     this.keyable            = keyable;
     this.curves             = animatable.GetCurves();
     this.posRect            = posRect;
     Undo.undoRedoPerformed += () => { RefreshCurves(); };
     Init();
 }
Ejemplo n.º 5
0
        public static void Draw3DCurve(IAnimatableData animatable, IKeyable keyable, Transform transformContext, float time, float timeSpan = 50f)
        {
            CurveEditor3DRenderer instance = null;

            if (!cache.TryGetValue(animatable, out instance))
            {
                cache[animatable] = instance = new CurveEditor3DRenderer();
            }
            instance.Draw3DCurve(animatable, keyable, transformContext, time, timeSpan);
        }
Ejemplo n.º 6
0
        public static void DrawDopeSheet(IAnimatableData animatable, IKeyable keyable, Rect rect, float startTime, float length, bool highlightRange = true)
        {
            DopeSheetRenderer dopeSheet = null;

            if (!cache.TryGetValue(animatable, out dopeSheet))
            {
                cache[animatable] = dopeSheet = new DopeSheetRenderer(animatable, keyable);
            }
            dopeSheet.DrawDopeSheet(animatable, keyable, rect, startTime, length, highlightRange);
        }
Ejemplo n.º 7
0
        public static void DrawCurves(IAnimatableData animatable, IKeyable keyable, Rect posRect, Rect timeRect)
        {
            CurveRenderer instance = null;

            if (!cache.TryGetValue(animatable, out instance))
            {
                cache[animatable] = instance = new CurveRenderer(animatable, keyable, posRect);
            }
            instance.Draw(posRect, timeRect);
        }
Ejemplo n.º 8
0
        public static void FrameAllCurvesOf(IAnimatableData animatable)
        {
            CurveRenderer instance = null;

            if (!cache.TryGetValue(animatable, out instance))
            {
                return;
            }
            instance.RecalculateBounds();
            instance.FrameClip(true, true);
        }
Ejemplo n.º 9
0
            public DopeSheetRenderer(IAnimatableData animatable, IKeyable keyable)
            {
                this.animatable = animatable;
                this.allCurves  = animatable.GetCurves();
                this.keyable    = keyable;
                RefreshDopeKeys(animatable);
                Undo.undoRedoPerformed += () => { ResetInteraction(); refreshDopeKeys = true; };

#if UNITY_2018_3_OR_NEWER
                UnityEditor.Experimental.SceneManagement.PrefabStage.prefabStageClosing += (stage) => { refreshDopeKeys = true; };
#endif
            }
Ejemplo n.º 10
0
            ///Should be called after each change to the curves
            public void RefreshDopeKeys(IAnimatableData animatable)
            {
                //get all curve keys
                allCurves = animatable.GetCurves();
                allKeys   = new List <Keyframe>();
                for (var i = 0; i < allCurves.Length; i++)
                {
                    allKeys.AddRange(allCurves[i].keys);
                    //cache wrapmode to parameters
                    if (animatable is AnimatedParameter && i == 0)
                    {
                        preWrapMode  = allCurves[i].preWrapMode;
                        postWrapMode = allCurves[i].postWrapMode;
                    }
                }
                allKeys = allKeys.OrderBy(k => k.time).ToList();

                //create dope key times and cache related data
                currentTimes = new List <float>();
                tangentModes = new List <TangentMode>();
                keyLabels    = new List <string>();
                for (var i = 0; i < allKeys.Count; i++)
                {
                    var key = allKeys[i];
                    if (!currentTimes.Any(t => Mathf.Abs(t - key.time) <= KEY_PROXIMITY_TOLERANCE))
                    {
                        currentTimes.Add(key.time);

                        //cache tangent mode
                        var keyTangent = CurveUtility.GetKeyTangentMode(key);
                        foreach (var otherKey in allKeys.Where(k => k.time == key.time))
                        {
                            var otherKeyTangent = CurveUtility.GetKeyTangentMode(otherKey);
                            if (otherKeyTangent != keyTangent)
                            {
                                keyTangent = TangentMode.Editable;
                                break;
                            }
                        }
                        tangentModes.Add(keyTangent);

                        //cache key labels
                        keyLabels.Add(string.Format("<size=8>{0}</size>", animatable.GetKeyLabel(key.time)));
                    }
                }
            }
Ejemplo n.º 11
0
        //Refresh dopesheet keys of target animatable.
        static void RefreshDopeKeysOf(IAnimatableData animatable)
        {
            DopeSheetRenderer dopeSheet = null;

            if (cache.TryGetValue(animatable, out dopeSheet))
            {
                dopeSheet.RefreshDopeKeys(animatable);
            }

            if (animatable is AnimationDataCollection)
            {
                var data = (AnimationDataCollection)animatable;
                if (data.animatedParameters != null)
                {
                    foreach (var animParam in data.animatedParameters)
                    {
                        if (cache.TryGetValue(animParam, out dopeSheet))
                        {
                            dopeSheet.RefreshDopeKeys(animParam);
                        }
                    }
                }
            }

            if (animatable is AnimatedParameter)
            {
                foreach (var pair in cache)
                {
                    if (pair.Key is AnimationDataCollection)
                    {
                        var data = (AnimationDataCollection)pair.Key;
                        if (data.animatedParameters != null && data.animatedParameters.Contains((AnimatedParameter)animatable))
                        {
                            pair.Value.RefreshDopeKeys(pair.Key);
                            break;
                        }
                    }
                }
            }
        }
Ejemplo n.º 12
0
            ///Display curves that belong to serializeContext and transformContext parent, at time and with timeSpan.
            public void Draw3DCurve(IAnimatableData animatable, IKeyable keyable, Transform transformContext, float time, float timeSpan = 50f)
            {
                this.animatable = animatable;
                // this.keyable = keyable;

                var curves = animatable.GetCurves();

                if (curves == null || curves.Length != 3)
                {
                    return;
                }

                var curveX = curves[0];
                var curveY = curves[1];
                var curveZ = curves[2];

                if (curveX.length < 2 || curveY.length < 2 || curveZ.length < 2)
                {
                    return;
                }

                if (curveX.length != curveY.length || curveY.length != curveZ.length)
                {
                    return;
                }


                var serializeContext = keyable as Object;
                var e = Event.current;

                var start = (float)Mathf.FloorToInt(time - (timeSpan / 2));
                var end   = (float)Mathf.CeilToInt(time + (timeSpan / 2));

                start = Mathf.Max(start, Mathf.Min(curveX[0].time, curveY[0].time, curveZ[0].time));
                end   = Mathf.Min(end, Mathf.Max(curveX[curveX.length - 1].time, curveY[curveY.length - 1].time, curveZ[curveZ.length - 1].time));

                if (curveX.length != lastCurveLength)
                {
                    lastCurveLength = curveX.length;
                    kIndex          = -1;
                }

                //1. Keyframes.
                for (var k = 0; k < curveX.length; k++)
                {
                    EditorGUI.BeginChangeCheck();
                    var forceChanged = false;

                    var keyX = curveX[k];
                    var keyY = curveY[k];
                    var keyZ = curveZ[k];

                    if (keyX.time < start)
                    {
                        continue;
                    }
                    if (keyX.time > end)
                    {
                        break;
                    }

                    var tangentModeX     = CurveUtility.GetKeyTangentMode(keyX);
                    var tangentModeY     = CurveUtility.GetKeyTangentMode(keyY);
                    var tangentModeZ     = CurveUtility.GetKeyTangentMode(keyZ);
                    var haveSameTangents = tangentModeX == tangentModeY && tangentModeY == tangentModeZ;
                    var tangentMode      = haveSameTangents ? tangentModeX : TangentMode.Editable;
                    var isBroken         = CurveUtility.GetKeyBroken(keyX) && CurveUtility.GetKeyBroken(keyY) && CurveUtility.GetKeyBroken(keyZ);

                    var pos = new Vector3(keyX.value, keyY.value, keyZ.value);

                    if (transformContext != null)
                    {
                        pos = transformContext.TransformPoint(pos);
                    }

                    Handles.Label(pos, keyX.time.ToString("0.00"));

                    ///MOUSE EVENTS
                    var screenPos = HandleUtility.WorldToGUIPoint(pos);
                    if (((Vector2)screenPos - e.mousePosition).magnitude < 10)
                    {
                        if (e.type == EventType.MouseDown)
                        {
                            if (e.button == 0 && kIndex != k)
                            {
                                kIndex = k;
                                GUIUtility.hotControl = 0;
                                SceneView.RepaintAll();
                                e.Use();
                            }

                            if (e.button == 1 && kIndex == k)
                            {
                                var menu = new GenericMenu();
                                menu.AddItem(new GUIContent("Jump Time Here"), false, () => { keyable.root.currentTime = curveX[kIndex].time + keyable.startTime; });
                                menu.AddItem(new GUIContent("Smooth"), tangentMode == TangentMode.Smooth, () => { contextAction = ContextAction.SetTangentMode; contextTangentMode = TangentMode.Smooth; });
                                menu.AddItem(new GUIContent("Linear"), tangentMode == TangentMode.Linear, () => { contextAction = ContextAction.SetTangentMode; contextTangentMode = TangentMode.Linear; });
                                menu.AddItem(new GUIContent("Constant"), tangentMode == TangentMode.Constant, () => { contextAction = ContextAction.SetTangentMode; contextTangentMode = TangentMode.Constant; });
                                menu.AddItem(new GUIContent("Editable"), tangentMode == TangentMode.Editable, () => { contextAction = ContextAction.SetTangentMode; contextTangentMode = TangentMode.Editable; });
                                if (tangentMode == TangentMode.Editable)
                                {
                                    menu.AddItem(new GUIContent("Tangents/Connected"), !isBroken, () => { contextAction = ContextAction.SetBrokenMode; contextBrokenMode = false; });
                                    menu.AddItem(new GUIContent("Tangents/Broken"), isBroken, () => { contextAction = ContextAction.SetBrokenMode; contextBrokenMode = true; });
                                }
                                menu.AddSeparator("/");
                                menu.AddItem(new GUIContent("Delete"), false, () => { contextAction = ContextAction.Delete; });
                                menu.ShowAsContext();
                                e.Use();
                            }
                        }
                    }

                    ///APPLY CONTEXT ACTIONS
                    if (contextAction != ContextAction.None && k == kIndex)
                    {
                        var _contextAction = contextAction;
                        contextAction = ContextAction.None;
                        forceChanged  = true;
                        if (_contextAction == ContextAction.SetBrokenMode)
                        {
                            Undo.RecordObject(serializeContext, "Animation Curve Change");
                            curveX.SetKeyBroken(kIndex, contextBrokenMode);
                            curveY.SetKeyBroken(kIndex, contextBrokenMode);
                            curveZ.SetKeyBroken(kIndex, contextBrokenMode);

                            NotifyChange();
                            return;
                        }

                        if (_contextAction == ContextAction.SetTangentMode)
                        {
                            Undo.RecordObject(serializeContext, "Animation Curve Change");
                            curveX.SetKeyTangentMode(kIndex, contextTangentMode);
                            curveY.SetKeyTangentMode(kIndex, contextTangentMode);
                            curveZ.SetKeyTangentMode(kIndex, contextTangentMode);

                            NotifyChange();
                            return;
                        }

                        if (_contextAction == ContextAction.Delete)
                        {
                            Undo.RecordObject(serializeContext, "Animation Curve Change");
                            curveX.RemoveKey(k);
                            curveY.RemoveKey(k);
                            curveZ.RemoveKey(k);
                            kIndex = -1;

                            NotifyChange();
                            return;
                        }
                    }


                    ///POSITION
                    var pointSize = HandleUtility.GetHandleSize(pos) * 0.05f;
                    var newValue  = pos;
                    if (kIndex == k)
                    {
                        if (Tools.current == Tool.Move)
                        {
                            newValue = Handles.PositionHandle(pos, Quaternion.identity);
                        }
                        else
                        {
                            newValue = Handles.FreeMoveHandle(pos, Quaternion.identity, pointSize, Vector3.zero, Handles.RectangleCap);
                        }
                    }
                    var cam = SceneView.lastActiveSceneView.camera;
                    Handles.RectangleCap(0, pos, cam.transform.rotation, pointSize);

                    if (transformContext != null)
                    {
                        newValue = transformContext.InverseTransformPoint(newValue);
                    }

                    keyX.value = newValue.x;
                    keyY.value = newValue.y;
                    keyZ.value = newValue.z;


                    ///TANGENTS
                    if (haveSameTangents && tangentMode == TangentMode.Editable)
                    {
                        if (kIndex == k)
                        {
                            if (k != 0)
                            {
                                var inHandle = new Vector3(-keyX.inTangent, -keyY.inTangent, -keyZ.inTangent);
                                inHandle /= HANDLE_DISTANCE_COMPENSATION;
                                inHandle  = newValue + inHandle;
                                if (transformContext != null)
                                {
                                    inHandle = transformContext.TransformPoint(inHandle);
                                }
                                var handleSize  = HandleUtility.GetHandleSize(inHandle) * 0.05f;
                                var newInHandle = Handles.FreeMoveHandle(inHandle, Quaternion.identity, handleSize, Vector3.zero, Handles.CircleCap);
                                Handles.DrawLine(pos, newInHandle);
                                if (transformContext != null)
                                {
                                    newInHandle = transformContext.InverseTransformPoint(newInHandle);
                                }

                                newInHandle   -= newValue;
                                newInHandle   *= HANDLE_DISTANCE_COMPENSATION;
                                keyX.inTangent = -newInHandle.x;
                                keyY.inTangent = -newInHandle.y;
                                keyZ.inTangent = -newInHandle.z;
                                if (!isBroken)
                                {
                                    keyX.outTangent = keyX.inTangent;
                                    keyY.outTangent = keyY.inTangent;
                                    keyZ.outTangent = keyZ.inTangent;
                                }
                            }

                            if (k < curveX.length - 1)
                            {
                                var outHandle = new Vector3(keyX.outTangent, keyY.outTangent, keyZ.outTangent);
                                outHandle /= HANDLE_DISTANCE_COMPENSATION;
                                outHandle  = newValue + outHandle;
                                if (transformContext != null)
                                {
                                    outHandle = transformContext.TransformPoint(outHandle);
                                }
                                var handleSize   = HandleUtility.GetHandleSize(outHandle) * 0.05f;
                                var newOutHandle = Handles.FreeMoveHandle(outHandle, Quaternion.identity, handleSize, Vector3.zero, Handles.CircleCap);
                                Handles.DrawLine(pos, newOutHandle);
                                if (transformContext != null)
                                {
                                    newOutHandle = transformContext.InverseTransformPoint(newOutHandle);
                                }
                                newOutHandle   -= newValue;
                                newOutHandle   *= HANDLE_DISTANCE_COMPENSATION;
                                keyX.outTangent = newOutHandle.x;
                                keyY.outTangent = newOutHandle.y;
                                keyZ.outTangent = newOutHandle.z;
                                if (!isBroken)
                                {
                                    keyX.inTangent = keyX.outTangent;
                                    keyY.inTangent = keyY.outTangent;
                                    keyZ.inTangent = keyZ.outTangent;
                                }
                            }
                        }
                    }

                    ///APPLY
                    if (EditorGUI.EndChangeCheck() || forceChanged)
                    {
                        Undo.RecordObject(serializeContext, "Animation Curve Change");
                        curveX.MoveKey(k, keyX);
                        curveY.MoveKey(k, keyY);
                        curveZ.MoveKey(k, keyZ);
                        EditorUtility.SetDirty(serializeContext);
                        NotifyChange();
                    }
                }


                ///2. Motion Path
                Handles.color = Prefs.motionPathsColor;
                var lastDrawnPos = Vector3.zero;

                for (var t = start; t <= end; t += DRAW_RESOLUTION)
                {
                    var pos     = new Vector3(curveX.Evaluate(t), curveY.Evaluate(t), curveZ.Evaluate(t));
                    var nextPos = new Vector3(curveX.Evaluate(t + DRAW_RESOLUTION), curveY.Evaluate(t + DRAW_RESOLUTION), curveZ.Evaluate(t + DRAW_RESOLUTION));

                    if (transformContext != null)
                    {
                        pos     = transformContext.TransformPoint(pos);
                        nextPos = transformContext.TransformPoint(nextPos);
                    }

                    if ((pos - lastDrawnPos).magnitude > DRAW_THRESHOLD)
                    {
                        lastDrawnPos = pos;
                        Handles.SphereCap(0, pos, Quaternion.identity, 0.02f);
                        Handles.DrawLine(pos, nextPos);
                    }
                }
                Handles.color = Color.white;


                ///3. GUI
                if (kIndex >= 0)
                {
                    var guiRect = new Rect(Screen.width - 300, Screen.height - 170, 280, 130);
                    var kx      = curveX[kIndex];
                    var ky      = curveY[kIndex];
                    var kz      = curveZ[kIndex];
                    EditorGUI.BeginChangeCheck();
                    {
                        Handles.BeginGUI();
                        GUILayout.BeginArea(guiRect);
                        EditorTools.BeginBody("Keyframe Parameters");
                        kx.value = EditorGUILayout.FloatField("X", kx.value);
                        ky.value = EditorGUILayout.FloatField("Y", ky.value);
                        kz.value = EditorGUILayout.FloatField("Z", kz.value);

                        GUI.enabled = CurveUtility.GetKeyTangentMode(kx) == TangentMode.Editable;
                        var inTangent = new Vector3(kx.inTangent, ky.inTangent, kz.inTangent);
                        inTangent    = EditorGUILayout.Vector3Field("", inTangent);
                        kx.inTangent = inTangent.x;
                        ky.inTangent = inTangent.y;
                        kz.inTangent = inTangent.z;

                        GUI.enabled = CurveUtility.GetKeyBroken(kx);
                        var outTangent = new Vector3(kx.outTangent, ky.outTangent, kz.outTangent);
                        outTangent    = EditorGUILayout.Vector3Field("", outTangent);
                        kx.outTangent = outTangent.x;
                        ky.outTangent = outTangent.y;
                        kz.outTangent = outTangent.z;

                        GUI.enabled = true;

                        EditorTools.EndBody();
                        GUILayout.EndArea();
                        Handles.EndGUI();
                    }
                    if (EditorGUI.EndChangeCheck())
                    {
                        Undo.RecordObject(serializeContext, "Animation Curve Change");
                        curveX.MoveKey(kIndex, kx);
                        curveY.MoveKey(kIndex, ky);
                        curveZ.MoveKey(kIndex, kz);
                        EditorUtility.SetDirty(serializeContext);
                        NotifyChange();
                    }
                }


                /*
                 *              for (var k = 0; k < curveX.length - 1; k++){
                 *                  var keyX = curveX[k];
                 *                  var keyY = curveY[k];
                 *                  var keyZ = curveZ[k];
                 *                  var nextKeyX = curveX[k+1];
                 *                  var nextKeyY = curveY[k+1];
                 *                  var nextKeyZ = curveZ[k+1];
                 *
                 *                  var t = new Vector3(keyX.time, keyY.time, keyZ.time);
                 *                  var nextT = new Vector3(nextKeyX.time, nextKeyY.time, nextKeyZ.time);
                 *
                 *                  var tangent = new Vector3( keyX.outTangent, keyY.outTangent, keyZ.outTangent );
                 *                  var nextTangent = new Vector3( nextKeyX.inTangent, nextKeyY.inTangent, nextKeyZ.inTangent );
                 *
                 *                  var pos = new Vector3( keyX.value, keyY.value, keyZ.value );
                 *                  var nextPos = new Vector3( nextKeyX.value, nextKeyY.value, nextKeyZ.value );
                 *
                 *                  if (transformContext != null){
                 *                      pos = transformContext.TransformPoint(pos);
                 *                      nextPos = transformContext.TransformPoint(nextPos);
                 *                  }
                 *
                 *                  var num = (nextT - t) * 0.333333f;
                 *                  var tangentPos = new Vector3( pos.x + num.x * tangent.x, pos.y + num.y * tangent.y, pos.z + num.z * tangent.z );
                 *                  var nextTangentPos = new Vector3( nextPos.x - num.x * nextTangent.x, nextPos.y - num.y * nextTangent.y, nextPos.z - num.z * nextTangent.z );
                 *
                 *                  Handles.DrawBezier(pos, nextPos, tangentPos, nextTangentPos, Prefs.motionPathsColor, null, 1.5f);
                 *              }
                 */
            }
Ejemplo n.º 13
0
            public void DrawDopeSheet(IAnimatableData animatable, IKeyable keyable, Rect rect, float startTime, float length, bool highlightRange)
            {
                this.length     = length;
                this.rect       = rect;
                this.width      = rect.width;
                this.startTime  = startTime;
                this.animatable = animatable;
                this.allCurves  = animatable.GetCurves();
                this.keyable    = keyable;
                var e = Event.current;

                //no curves?
                if (allCurves == null || allCurves.Length == 0)
                {
                    GUI.Label(new Rect(rect.x, rect.y, rect.width, rect.height), "---");
                    return;
                }

                //if flag is true refresh all dopesheets of the same IKeyable
                if (refreshDopeKeys)
                {
                    refreshDopeKeys = false;
                    DopeSheetEditor.RefreshDopeKeysOf(animatable);
                }

                //range graphics
                if (highlightRange && currentTimes.Count > 0)
                {
                    var firstKeyPos = TimeToPos(currentTimes.FirstOrDefault());
                    var lastKeyPos  = TimeToPos(currentTimes.LastOrDefault());

                    if (Mathf.Abs(firstKeyPos - lastKeyPos) > 0)
                    {
                        var rangeRect = Rect.MinMaxRect(firstKeyPos - 8, rect.yMin, lastKeyPos + 8, rect.yMax);
                        rangeRect.xMin = Mathf.Max(rangeRect.xMin, rect.xMin);
                        rangeRect.xMax = Mathf.Min(rangeRect.xMax, rect.xMax);
                        if (rangeRect.width > 5)
                        {
                            GUI.color = EditorGUIUtility.isProSkin ? new Color(0f, 0.5f, 0.5f, 0.4f) : new Color(1, 1, 1, 0.5f);
                            GUI.Box(rangeRect, string.Empty, Slate.Styles.clipBoxStyle);
                            GUI.color = Color.white;
                        }

                        if (preWrapMode != WrapMode.ClampForever)
                        {
                            var r = Rect.MinMaxRect(rect.xMin, rect.yMin, firstKeyPos, rect.yMax);
                            if (r.width > 16)
                            {
                                GUI.color = new Color(1, 1, 1, 0.5f);
                                var r2 = new Rect(0, 0, 16, 16);
                                r2.center = r.center;
                                Texture2D icon = null;
                                if (preWrapMode == WrapMode.Loop)
                                {
                                    icon = Styles.loopIcon;
                                }
                                if (preWrapMode == WrapMode.PingPong)
                                {
                                    icon = Styles.pingPongIcon;
                                }
                                if (icon != null)
                                {
                                    GUI.Box(r2, icon, GUIStyle.none);
                                }
                                GUI.color = Color.white;
                            }
                        }

                        if (postWrapMode != WrapMode.ClampForever)
                        {
                            var r = Rect.MinMaxRect(lastKeyPos, rect.yMin, rect.xMax, rect.yMax);
                            if (r.width > 16)
                            {
                                GUI.color = new Color(1, 1, 1, 0.25f);
                                var r2 = new Rect(0, 0, 16, 16);
                                r2.center = r.center;
                                Texture2D icon = null;
                                if (postWrapMode == WrapMode.Loop)
                                {
                                    icon = Styles.loopIcon;
                                }
                                if (postWrapMode == WrapMode.PingPong)
                                {
                                    icon = Styles.pingPongIcon;
                                }
                                if (icon != null)
                                {
                                    GUI.Box(r2, icon, GUIStyle.none);
                                }
                                GUI.color = Color.white;
                            }
                        }
                    }
                }

                //bg graphics (just a horizontal line)
                GUI.color = new Color(0, 0, 0, 0.1f);
                var center   = rect.y + (rect.height / 2);
                var lineRect = Rect.MinMaxRect(rect.x, center - 1, rect.xMax, center + 1);

                GUI.DrawTexture(lineRect, Slate.Styles.whiteTexture);
                GUI.color = Color.white;

                //selection rect graphics
                if (timeSelectionRect != null)
                {
                    GUI.Box(pixelSelectionRect, string.Empty);
                    GUI.color = new Color(0.5f, 0.5f, 1, 0.25f);
                    GUI.DrawTexture(pixelSelectionRect, Slate.Styles.whiteTexture);
                    GUI.color = Color.white;
                }


                //draw the dopekeys
                var tangentMode = TangentMode.Editable;

                for (var t = 0; t < currentTimes.Count; t++)
                {
                    var time = currentTimes[t];
                    //ignore if out of view range (+- some extra offset)
                    if (time < startTime - 0.1f || time > startTime + length + 0.1f)
                    {
                        continue;
                    }

                    //DopeKey graphics/icon
                    var icon = Slate.Styles.dopeKey;
                    if (Prefs.keyframesStyle == Prefs.KeyframesStyle.PerTangentMode)
                    {
                        tangentMode = tangentModes[t];
                        if (tangentMode != TangentMode.Editable)
                        {
                            if (tangentMode == TangentMode.Smooth)
                            {
                                icon = Slate.Styles.dopeKeySmooth;
                            }
                            if (tangentMode == TangentMode.Constant)
                            {
                                icon = Slate.Styles.dopeKeyConstant;
                            }
                            if (tangentMode == TangentMode.Linear)
                            {
                                icon = Slate.Styles.dopeKeyLinear;
                            }
                        }
                    }

                    var dopeKeyRect = new Rect(0, 0, icon.width, icon.height);
                    dopeKeyRect.center = new Vector2(TimeToPos(time), rect.center.y);
                    var isSelected = t == pickIndex || (rectSelectedIndeces != null && rectSelectedIndeces.Contains(t));
                    GUI.color = isSelected ? new Color(0.6f, 0.6f, 1) : Color.white;
                    GUI.DrawTexture(dopeKeyRect, icon);
                    GUI.color = Color.white;


                    //key value label
                    if (Prefs.showDopesheetKeyValues)
                    {
                        var nextPos        = t < currentTimes.Count - 1 ? TimeToPos(currentTimes[t + 1]) : TimeToPos(length);
                        var valueLabelRect = Rect.MinMaxRect(dopeKeyRect.xMax, rect.yMin - 3, nextPos - dopeKeyRect.width / 2, rect.yMax);
                        if (valueLabelRect.width > 20)
                        {
                            GUI.Label(valueLabelRect, keyLabels[t], Slate.Styles.leftLabel);
                        }
                    }


                    //do the following only if we dont have a rect selection
                    if (timeSelectionRect == null)
                    {
                        //pick the key
                        if (e.type == EventType.MouseDown && dopeKeyRect.Contains(e.mousePosition))
                        {
                            prePickTimes = new List <float>(currentTimes);
                            pickIndex    = t;
                            if (e.clickCount == 2)
                            {
                                keyable.root.currentTime       = time + keyable.startTime;
                                CutsceneUtility.selectedObject = keyable;
                            }
                            e.Use();
                        }

                        //single key context menu
                        if (e.type == EventType.MouseUp && e.button == 1 && dopeKeyRect.Contains(e.mousePosition))
                        {
                            DoSingleKeyContextMenu(e, time, tangentMode);
                            e.Use();
                        }
                    }
                }


                //drag the picked key if any. Shift drags all next to it as well
                if (pickIndex != -1)
                {
                    var controlID = GUIUtility.GetControlID(FocusType.Passive);
                    var eventType = e.GetTypeForControl(controlID);
                    if (eventType == EventType.MouseDrag && e.button == 0)
                    {
                        GUIUtility.hotControl = controlID;
                        var lastTime = currentTimes[pickIndex];
                        var newTime  = PosToTime(e.mousePosition.x);
                        newTime = Mathf.Round(newTime / Prefs.snapInterval) * Prefs.snapInterval;
                        newTime = Mathf.Clamp(newTime, startTime, startTime + length);
                        if (e.shift)
                        {
                            var max = pickIndex > 0 ? currentTimes[pickIndex - 1] + Prefs.snapInterval : startTime;
                            newTime = Mathf.Max(newTime, max);
                            foreach (var time in currentTimes.Where(k => k > lastTime))
                            {
                                var index = currentTimes.IndexOf(time);
                                currentTimes[index] += newTime - lastTime;
                            }
                        }
                        currentTimes[pickIndex] = newTime;
                    }

                    //apply the changes when mouse up and deselect key
                    if (eventType == EventType.MouseUp)
                    {
                        GUIUtility.hotControl = 0;
                        pickIndex             = -1;
                        Apply();
                        e.Use();
                    }
                }



                //Multikey selection, dragging and retiming
                if (pickIndex == -1)
                {
                    var retimeInRect  = Rect.MinMaxRect(pixelSelectionRect.xMin, pixelSelectionRect.yMin, pixelSelectionRect.xMin + 4, pixelSelectionRect.yMax);
                    var retimeOutRect = Rect.MinMaxRect(pixelSelectionRect.xMax - 4, pixelSelectionRect.yMin, pixelSelectionRect.xMax, pixelSelectionRect.yMax);

                    var controlID = GUIUtility.GetControlID(FocusType.Passive);
                    var eventType = e.GetTypeForControl(controlID);

                    if (e.rawType == EventType.MouseDown && !rect.Contains(e.mousePosition))
                    {
                        ResetInteraction();
                    }

                    if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
                    {
                        //if no rect selection, start one.
                        if (timeSelectionRect == null)
                        {
                            if (e.button == 0)
                            {
                                selectionStartPos = e.mousePosition.x;
                                e.Use();
                            }
                        }
                        else
                        {
                            //if we have a rect and mouse contains it, initialize original values and keys.
                            if (pixelSelectionRect.Contains(e.mousePosition))
                            {
                                prePickTimes          = new List <float>(currentTimes);
                                startDragTime         = (float)PosToTime(e.mousePosition.x);
                                preScaleSelectionRect = timeSelectionRect.Value;
                                rectSelectedIndeces   = new List <int>();
                                var temp = timeSelectionRect.Value;
                                for (var i = 0; i < currentTimes.Count; i++)
                                {
                                    if (currentTimes[i] >= temp.xMin && currentTimes[i] <= temp.xMax)
                                    {
                                        rectSelectedIndeces.Add(i);
                                    }
                                }

                                isRetiming = e.button == 0 && retimeInRect.Contains(e.mousePosition) || retimeOutRect.Contains(e.mousePosition);
                                e.Use();

                                //if we have a rect, but mouse is outside, clear all and reset values.
                            }
                            else
                            {
                                ResetInteraction();
                                e.Use();
                            }
                        }
                    }


                    //create the selection rect
                    if (eventType == EventType.MouseDrag && selectionStartPos != null)
                    {
                        GUIUtility.hotControl = controlID;
                        var a    = PosToTime(selectionStartPos.Value);
                        var b    = PosToTime(e.mousePosition.x);
                        var xMin = Mathf.Min(a, b);
                        var xMax = Mathf.Max(a, b);
                        xMin = Mathf.Max(xMin, startTime);
                        xMax = Mathf.Min(xMax, startTime + length);
                        timeSelectionRect = Mathf.Abs(a - b) >= 0.001f ? Rect.MinMaxRect(xMin, rect.yMin, xMax, rect.yMax) : (Rect?)null;
                    }

                    //draw the selection rect
                    if (timeSelectionRect != null)
                    {
                        EditorGUIUtility.AddCursorRect(retimeInRect, MouseCursor.ResizeHorizontal);
                        EditorGUIUtility.AddCursorRect(retimeOutRect, MouseCursor.ResizeHorizontal);
                        EditorGUIUtility.AddCursorRect(pixelSelectionRect, MouseCursor.Link);
                        GUI.Box(retimeInRect, string.Empty);
                        GUI.Box(retimeOutRect, string.Empty);
                    }

                    //move/retime the selection rect
                    if (eventType == EventType.MouseDrag && timeSelectionRect != null && e.button == 0 && (startDragTime != null || isRetiming))
                    {
                        GUIUtility.hotControl = controlID;
                        var temp        = timeSelectionRect.Value;
                        var pointerTime = PosToTime(e.mousePosition.x);

                        //retime
                        if (isRetiming)
                        {
                            var retimeIn = Mathf.Abs(pointerTime - temp.x) < Mathf.Abs(pointerTime - temp.xMax);
                            if (retimeIn)
                            {
                                temp.xMin = Mathf.Max(pointerTime, 0);
                            }
                            else
                            {
                                temp.xMax = pointerTime;
                            }

                            foreach (var index in rectSelectedIndeces)
                            {
                                var preTime = prePickTimes[index];
                                var norm    = Mathf.InverseLerp(preScaleSelectionRect.xMin, preScaleSelectionRect.xMax, preTime);
                                currentTimes[index] = Mathf.Lerp(temp.xMin, temp.xMax, norm);
                            }

                            //move
                        }
                        else
                        {
                            if (startDragTime != null)
                            {
                                var delta = pointerTime - (float)startDragTime;
                                if (temp.x + delta >= 0)
                                {
                                    foreach (var index in rectSelectedIndeces)
                                    {
                                        currentTimes[index] += delta;
                                    }
                                    temp.x       += delta;
                                    startDragTime = (float)pointerTime;
                                }
                            }
                        }

                        timeSelectionRect = temp;
                    }

                    //Apply all changes and reset values on MouseUp within or outside the rect
                    if (eventType == EventType.MouseUp)
                    {
                        //mouse up when making a selection
                        if (selectionStartPos != null)
                        {
                            GUIUtility.hotControl = 0;
                            selectionStartPos     = null;
                        }

                        //mouse up when dragging or retiming the existing selection
                        if (timeSelectionRect != null && (startDragTime != null || isRetiming))
                        {
                            GUIUtility.hotControl = 0;
                            Apply();
                            isRetiming    = false;
                            startDragTime = null;
                            if (e.button == 0)
                            {
                                rectSelectedIndeces = null;
                            }
                        }
                    }

                    //Context click
                    if (eventType == EventType.ContextClick && rect.Contains(e.mousePosition))
                    {
                        if (pixelSelectionRect.Contains(e.mousePosition))
                        {
                            DoMultiKeyContextMenu(e);
                            e.Use();
                        }
                        else
                        {
                            DoVoidContextMenu(e);
                            e.Use();
                        }
                    }
                }
            }