///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(); } } } } }
///Refresh animation editors (dopesheet, curves) of targer animatable public static void RefreshAllAnimationEditorsOf(IAnimatableData animatable) { if (onRefreshAllAnimationEditors != null) { onRefreshAllAnimationEditors(animatable); } }
public DopeSheetRenderer(IAnimatableData animatable) { this.animatable = animatable; this.allCurves = animatable.GetCurves(); RefreshDopeKeys(animatable); Undo.undoRedoPerformed += () => { ResetInteraction(); refreshDopeKeys = true; }; }
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(); }
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); }
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); }
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); }
public static void FrameAllCurvesOf(IAnimatableData animatable) { CurveRenderer instance = null; if (!cache.TryGetValue(animatable, out instance)) { return; } instance.RecalculateBounds(); instance.FrameClip(true, true); }
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 }
///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))); } } }
//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; } } } } }
///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); * } */ }
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(); } } } }