public override void OnTrackTimelineGUI(Rect posRect, Rect timeRect, float cursorTime, System.Func <float, float> TimeToPos) { var e = Event.current; var baseDopeRect = new Rect(posRect.xMin, posRect.yMin, posRect.width, defaultHeight); GUI.color = new Color(0.5f, 0.5f, 0.5f, 0.3f); GUI.Box(baseDopeRect, "", Slate.Styles.clipBoxHorizontalStyle); GUI.color = Color.white; DopeSheetEditor.DrawDopeSheet(this.animationData, this, baseDopeRect, timeRect.x, timeRect.width); if (showCurves) { var subDopeRect = Rect.MinMaxRect(posRect.xMin, posRect.yMin + defaultHeight, posRect.xMax, posRect.yMax); DoClipCurves(e, subDopeRect, timeRect, TimeToPos, this); } }
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(); } } } }
///Inline curve editor box static void DoCurveBox(AnimatedParameter animParam, IKeyable keyable, bool isRecording) { var e = Event.current; var keyableLength = keyable.GetLength(); var keyableTime = keyable.RootTimeToLocalTime(); GUILayout.Label("INVISIBLE TEXT", GUILayout.Height(0)); var lastRect = GUILayoutUtility.GetLastRect(); GUILayout.Space(250); var timeRect = new Rect(0, 0, keyableLength, 0); var posRect = new Rect(); if (e.type == EventType.Repaint || !fixedCurveRects.TryGetValue(animParam, out posRect)) { posRect = new Rect(lastRect.x, lastRect.yMax + 5, lastRect.width, 240); fixedCurveRects[animParam] = posRect; } GUI.color = EditorGUIUtility.isProSkin ? new Color(0, 0, 0, 0.5f) : new Color(0, 0, 0, 0.3f); GUI.Box(posRect, "", (GUIStyle)"textfield"); GUI.color = Color.white; var dragTimeRect = new Rect(posRect.x, posRect.y + 1, posRect.width, 10); GUI.Box(dragTimeRect, ""); if (dragTimeRect.Contains(e.mousePosition)) { EditorGUIUtility.AddCursorRect(dragTimeRect, MouseCursor.SplitResizeLeftRight); if (e.type == EventType.MouseDown && e.button == 0) { isDraggingTime = true; e.Use(); } } if (isDraggingTime) { var iLerp = Mathf.InverseLerp(posRect.x, posRect.xMax, e.mousePosition.x); keyable.root.currentTime = Mathf.Lerp(keyable.startTime, keyable.endTime, iLerp); } if (e.rawType == EventType.MouseUp) { isDraggingTime = false; } if (e.type == EventType.KeyDown && posRect.Contains(e.mousePosition)) { if (e.keyCode == KeyCode.Comma) { GUIUtility.keyboardControl = 0; keyable.root.currentTime = animParam.GetKeyPrevious(keyableTime) + keyable.startTime; e.Use(); } if (e.keyCode == KeyCode.Period) { GUIUtility.keyboardControl = 0; keyable.root.currentTime = animParam.GetKeyNext(keyableTime) + keyable.startTime; Event.current.Use(); } } var dopeRect = new Rect(posRect.x, dragTimeRect.yMax + 1, posRect.width, 16); Handles.color = Color.black.WithAlpha(0.2f); Handles.DrawLine(new Vector2(dopeRect.xMin, dopeRect.yMin), new Vector2(dopeRect.xMax, dopeRect.yMin)); Handles.DrawLine(new Vector2(dopeRect.xMin, dopeRect.yMax), new Vector2(dopeRect.xMax, dopeRect.yMax)); Handles.color = Color.white; DopeSheetEditor.DrawDopeSheet(animParam, keyable, dopeRect, 0, keyableLength); var curvesRect = new Rect(posRect.x, dopeRect.yMax, posRect.width, posRect.height - dopeRect.height - dragTimeRect.height); CurveEditor.DrawCurves(animParam, keyable, curvesRect, timeRect); if (isRecording) { var iLerp = Mathf.InverseLerp(keyable.startTime, keyable.endTime, keyable.root.currentTime); var lerp = Mathf.Lerp(posRect.x, posRect.xMax, iLerp); var a = new Vector3(lerp, posRect.y, 0); var b = new Vector3(lerp, posRect.yMax, 0); Handles.color = EditorGUIUtility.isProSkin ? Slate.Styles.recordingColor : Color.red; Handles.DrawAAPolyLine(a, b); Handles.color = Color.white; } }
protected void DoClipCurves(Event e, Rect posRect, Rect timeRect, System.Func <float, float> TimeToPos, IKeyable keyable) { //track expanded bg GUI.color = new Color(0, 0, 0, 0.08f); GUI.Box(posRect, string.Empty, Styles.timeBoxStyle); GUI.color = Color.white; if (((keyable == null) || !ReferenceEquals(keyable.parent, this)) && !ReferenceEquals(keyable, this)) { GUI.color = new Color(1, 1, 1, 0.3f); GUI.Label(posRect, "Select a Clip of this Track to view it's Animated Parameters here", Styles.centerLabel); GUI.color = Color.white; return; } var finalPosRect = posRect; var finalTimeRect = timeRect; //adjust rects if (keyable is ActionClip) { finalPosRect.xMin = Mathf.Max(posRect.xMin, TimeToPos(keyable.startTime)); finalPosRect.xMax = Mathf.Min(posRect.xMax, TimeToPos(keyable.endTime)); finalTimeRect.xMin = Mathf.Max(timeRect.xMin, keyable.startTime) - keyable.startTime; finalTimeRect.xMax = Mathf.Min(timeRect.xMax, keyable.endTime) - keyable.startTime; } //add some top/bottom margins finalPosRect.yMin += 1; finalPosRect.yMax -= 3; finalPosRect.width = Mathf.Max(finalPosRect.width, 5); //dark bg GUI.color = new Color(0.1f, 0.1f, 0.1f, 0.5f); GUI.DrawTexture(posRect, Styles.whiteTexture); GUI.color = Color.white; //out of view range if (keyable is ActionClip) { if (keyable.startTime > timeRect.xMax || keyable.endTime < timeRect.xMin) { return; } } //keyable bg GUI.color = UnityEditor.EditorGUIUtility.isProSkin? new Color(0.25f, 0.25f, 0.25f, 0.9f) : new Color(0.7f, 0.7f, 0.7f, 0.9f); GUI.Box(finalPosRect, string.Empty, Styles.clipBoxFooterStyle); GUI.color = Color.white; //if too small do nothing more if (finalPosRect.width <= 5) { return; } if (keyable is ActionClip && !(keyable as ActionClip).isValid) { GUI.Label(finalPosRect, "Clip Is Invalid", Styles.centerLabel); return; } if (keyable.animationData == null || !keyable.animationData.isValid) { if (keyable is ActionClip) { GUI.Label(finalPosRect, "Clip has no Animatable Parameters", Styles.centerLabel); } else { GUI.Label(finalPosRect, "Track has no Animated Properties. You can add some on the left side", Styles.centerLabel); } return; } if (inspectedParameterIndex >= keyable.animationData.animatedParameters.Count) { inspectedParameterIndex = -1; } //vertical guides from params to dopesheet if (inspectedParameterIndex == -1) { var yPos = PARAMS_TOP_MARGIN; for (var i = 0; i < keyable.animationData.animatedParameters.Count; i++) { // var animParam = keyable.animationData.animatedParameters[i]; var paramRect = new Rect(0, posRect.yMin + yPos, finalPosRect.xMin - 2, PARAMS_LINE_HEIGHT); yPos += PARAMS_LINE_HEIGHT + PARAMS_LINE_MARGIN; paramRect.yMin += 1f; paramRect.yMax -= 1f; GUI.color = new Color(0, 0.5f, 0.5f, 0.1f); GUI.DrawTexture(paramRect, Styles.whiteTexture); GUI.color = Color.white; } } //begin in group and neutralize rect GUI.BeginGroup(finalPosRect); finalPosRect = new Rect(0, 0, finalPosRect.width, finalPosRect.height); if (inspectedParameterIndex == -1) { var yPos = PARAMS_TOP_MARGIN; for (var i = 0; i < keyable.animationData.animatedParameters.Count; i++) { var animParam = keyable.animationData.animatedParameters[i]; var paramRect = new Rect(finalPosRect.xMin, finalPosRect.yMin + yPos, finalPosRect.width, PARAMS_LINE_HEIGHT); yPos += PARAMS_LINE_HEIGHT + PARAMS_LINE_MARGIN; paramRect.yMin += 1f; paramRect.yMax -= 1f; GUI.color = new Color(1, 1, 1, 0.3f); GUI.Box(paramRect, string.Empty); GUI.color = Color.white; if (animParam.enabled) { DopeSheetEditor.DrawDopeSheet(animParam, keyable, paramRect, finalTimeRect.x, finalTimeRect.width, true); } else { GUI.color = new Color(0, 0, 0, 0.2f); GUI.DrawTextureWithTexCoords(paramRect, Styles.stripes, new Rect(0, 0, paramRect.width / 7, paramRect.height / 7)); GUI.color = Color.white; } } } if (inspectedParameterIndex >= 0) { var animParam = keyable.animationData.animatedParameters[inspectedParameterIndex]; var dopeRect = finalPosRect; dopeRect.y += 4f; dopeRect.height = 16f; GUI.color = new Color(1, 1, 1, 0.3f); GUI.Box(dopeRect, string.Empty); GUI.color = Color.white; DopeSheetEditor.DrawDopeSheet(animParam, keyable, dopeRect, finalTimeRect.x, finalTimeRect.width, true); var curveRect = finalPosRect; curveRect.yMin = dopeRect.yMax + 4; CurveEditor.DrawCurves(animParam, keyable, curveRect, finalTimeRect); } //consume event if (e.type == EventType.MouseDown && finalPosRect.Contains(e.mousePosition)) { e.Use(); } GUI.EndGroup(); /* * //darken out of clip range time * //will use if I make curve editing full-width * if (Prefs.fullWidthCurveEditing){ * var darkBefore = Rect.MinMaxRect( posRect.xMin, posRect.yMin, Mathf.Max(posRect.xMin, TimeToPos(keyable.startTime)), posRect.yMax ); * var darkAfter = Rect.MinMaxRect( Mathf.Min(posRect.xMax, TimeToPos(keyable.endTime)), posRect.yMin, posRect.xMax, posRect.yMax ); * GUI.color = new Color(0.1f,0.1f,0.1f,0.6f); * GUI.DrawTexture(darkBefore, Styles.whiteTexture); * GUI.DrawTexture(darkAfter, Styles.whiteTexture); * GUI.color = Color.white; * } */ }