///Display an AnimatedParameter GUI
        public static void ShowParameter(AnimatedParameter animParam, IKeyable keyable, SerializedProperty serializedProperty = null)
        {
            //Calling this through a PropertyDrawer (thus serialized property != null), seems to have some mambo jumbo spacing.
            //This fixes that spacing so that both field and property parameter editors looks the same.
            GUILayout.Space(serializedProperty != null ? 0 : 2);

            if (!animParam.isValid)
            {
                GUILayout.Label("Animatable Parameter is invalid");
                return;
            }

            var e                = Event.current;
            var keyableLength    = keyable.endTime - keyable.startTime;
            var rootTime         = keyable.root.currentTime;
            var localTime        = Mathf.Clamp(rootTime - keyable.startTime, 0, keyableLength);
            var isRecording      = rootTime >= keyable.startTime && rootTime <= keyable.endTime && rootTime > 0;
            var foldOut          = EditorTools.GetObjectFoldOut(animParam);
            var hasAnyKey        = animParam.HasAnyKey();
            var hasKeyNow        = animParam.HasKey(localTime);
            var hasChanged       = animParam.HasChanged();
            var parameterEnabled = animParam.enabled;
            var lastRect         = new Rect();

            GUI.backgroundColor = new Color(0, 0.4f, 0.4f, 0.5f);
            GUILayout.BeginVertical(Slate.Styles.headerBoxStyle);
            GUI.backgroundColor = Color.white;

            GUILayout.BeginHorizontal();

            var sFold = foldOut ? "▼" : "▶";
            var sName = animParam.ToString();

            GUI.backgroundColor = hasAnyKey && parameterEnabled ? new Color(1, 0.6f, 0.6f) : Color.white;
            GUI.backgroundColor = hasAnyKey && parameterEnabled && isRecording ? Slate.Styles.recordingColor : GUI.backgroundColor;

            GUILayout.Label(sFold, GUILayout.Width(13));
            lastRect    = GUILayoutUtility.GetLastRect();
            GUI.enabled = !animParam.isExternal || isRecording;

            DoParameterField(string.Format("<b>{0}</b>", sName), animParam, localTime);

            GUI.enabled         = true;
            GUI.backgroundColor = Color.white;


            EditorGUIUtility.AddCursorRect(lastRect, MouseCursor.Link);
            if (e.type == EventType.MouseDown && e.button == 0 && lastRect.Contains(e.mousePosition))
            {
                EditorTools.SetObjectFoldOut(animParam, !foldOut);
                e.Use();
            }

            GUI.enabled = hasAnyKey && parameterEnabled;
            if (GUILayout.Button(Slate.Styles.previousKeyIcon, GUIStyle.none, GUILayout.Height(20), GUILayout.Width(16)))
            {
                keyable.root.currentTime = animParam.GetKeyPrevious(localTime) + keyable.startTime;
            }
            EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);


            GUI.enabled = parameterEnabled;
            GUI.color   = hasKeyNow && parameterEnabled ? new Color(1, 0.3f, 0.3f) : Color.white;
            GUI.color   = hasAnyKey && hasChanged ? Color.green : GUI.color;
            if (GUILayout.Button(Slate.Styles.keyIcon, GUIStyle.none, GUILayout.Height(20), GUILayout.Width(16)))
            {
                if (e.alt)     //temporary solution
                {
                    animParam.scriptExpression = "value";
                    EditorTools.SetObjectFoldOut(animParam, true);
                }
                else
                {
                    if (!hasKeyNow || hasChanged)
                    {
                        animParam.SetKeyCurrent(localTime);
                    }
                    else
                    {
                        animParam.RemoveKey(localTime);
                    }
                }
            }
            GUI.color = Color.white;
            EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);


            GUI.enabled = hasAnyKey && parameterEnabled;
            if (GUILayout.Button(Slate.Styles.nextKeyIcon, GUIStyle.none, GUILayout.Height(20), GUILayout.Width(16)))
            {
                keyable.root.currentTime = animParam.GetKeyNext(localTime) + keyable.startTime;
            }
            EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);

            GUILayout.Space(2);

            GUI.enabled = true;
            GUI.color   = Color.white.WithAlpha(animParam.enabled ? 1 : 0.5f);
            if (GUILayout.Button(Slate.Styles.gearIcon, GUIStyle.none, GUILayout.Height(20), GUILayout.Width(16)))
            {
                DoParamGearContextMenu(animParam, keyable);
            }
            GUI.color = Color.white;
            EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);

            GUI.enabled = true;
            GUILayout.EndHorizontal();

            //...
            GUILayout.EndVertical();

            var fade = EditorTools.GetObjectFoldOutFaded(animParam);

            if (EditorGUILayout.BeginFadeGroup(fade))
            {
                var hasExpression = animParam.hasActiveExpression;

                GUI.color = new Color(0.5f, 0.5f, 0.5f, 0.3f);
                GUILayout.BeginVertical(Slate.Styles.clipBoxFooterStyle);
                GUI.color = Color.white;

#if SLATE_USE_EXPRESSIONS
                GUILayout.BeginHorizontal();
                if (hasExpression)
                {
                    if (GUILayout.Button(Styles.expressionIcon, GUI.skin.label, GUILayout.Width(18)))
                    {
                        var menu = Slate.Expressions.ExpressionsMenuGenerator.GetExpressionEnvironmentMenu(animParam.GetExpressionEnvironment(), (template) => { animParam.scriptExpression = template; });
                        menu.ShowAsContext();
                    }
                    EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link);
                    animParam.scriptExpression = EditorGUILayout.DelayedTextField(animParam.scriptExpression);
                }
                GUILayout.EndHorizontal();

                if (hasExpression && animParam.compileException != null)
                {
                    EditorGUILayout.HelpBox(animParam.compileException.Message, MessageType.Error);
                }
#endif

                string info = null;
                if (!parameterEnabled)
                {
                    info = "Parameter is disabled or overriden.";
                }

                if (info == null && !hasAnyKey && !hasExpression)
                {
                    info = "Parameter is not yet animated. You can make it so by creating the first key.";
                }

                if (info == null && keyableLength == 0 && hasAnyKey)
                {
                    info = "Length of Clip is zero. Can not display Curve Editor.";
                }

                if (info == null && animParam.isExternal && !isRecording)
                {
                    info = "This Parameter can only be edited when time is within the clip range.";
                }

                if (info != null)
                {
                    GUILayout.Label(info);
                }
                else
                {
                    DoCurveBox(animParam, keyable, isRecording);
                }

                GUILayout.EndVertical();
                GUILayout.Space(5);
            }
            else
            {
#if SLATE_USE_EXPRESSIONS
                if (animParam.hasActiveExpression)
                {
                    GUI.color = new Color(0.5f, 0.5f, 0.5f, 0.3f);
                    GUILayout.BeginHorizontal(Styles.clipBoxFooterStyle);
                    GUI.color = Color.white;
                    GUILayout.Space(10);
                    GUILayout.Label(string.Format("<b>= </b><size=9>{0}</size>", animParam.scriptExpression));
                    GUILayout.EndHorizontal();
                }
#endif
            }

            EditorGUILayout.EndFadeGroup();
        }
Exemple #2
0
        //show selected clip animated parameters list info
        protected void DoParamsInfoGUI(Event e, Rect trackRect, IKeyable keyable, bool showAddPropertyButton)
        {
            //bg graphic
            var expansionRect = Rect.MinMaxRect(5, defaultHeight, trackRect.width - 3, finalHeight - 3);

            GUI.color = UnityEditor.EditorGUIUtility.isProSkin ? new Color(0.22f, 0.22f, 0.22f) : new Color(0.7f, 0.7f, 0.7f);
            GUI.DrawTexture(expansionRect, Styles.whiteTexture);
            GUI.color = new Color(0, 0, 0, 0.05f);
            GUI.Box(expansionRect, string.Empty, Styles.shadowBorderStyle);
            GUI.color = Color.white;


            //allow resize height
            if (inspectedParameterIndex >= 0)
            {
                var resizeRect = Rect.MinMaxRect(0, finalHeight - 4, trackRect.width, finalHeight);
                UnityEditor.EditorGUIUtility.AddCursorRect(resizeRect, UnityEditor.MouseCursor.ResizeVertical);
                GUI.color = Color.grey;
                GUI.DrawTexture(resizeRect, Styles.whiteTexture);
                GUI.color = Color.white;
                if (e.type == EventType.MouseDown && e.button == 0 && resizeRect.Contains(e.mousePosition))
                {
                    isResizingHeight = true; e.Use();
                }
                if (e.type == EventType.MouseDrag && isResizingHeight)
                {
                    customHeight += e.delta.y;
                }
                if (e.rawType == EventType.MouseUp)
                {
                    isResizingHeight = false;
                }
            }

            proposedHeight = 0f;

            if (((keyable == null) || !ReferenceEquals(keyable.parent, this)) && !ReferenceEquals(keyable, this))
            {
                GUI.Label(expansionRect, "No Clip Selected", Styles.centerLabel);
                inspectedParameterIndex = -1;
                return;
            }

            if (!showAddPropertyButton)
            {
                if (keyable is ActionClip && !(keyable as ActionClip).isValid)
                {
                    GUI.Label(expansionRect, "Clip Is Invalid", Styles.centerLabel);
                    return;
                }

                if (keyable.animationData == null || !keyable.animationData.isValid)
                {
                    if (keyable is ActionClip)
                    {
                        GUI.Label(expansionRect, "Clip Has No Animatable Parameters", Styles.centerLabel);
                        return;
                    }
                }
            }

            proposedHeight = defaultHeight + PARAMS_TOP_MARGIN;
            if (keyable.animationData != null && keyable.animationData.animatedParameters != null)
            {
                if (inspectedParameterIndex >= keyable.animationData.animatedParameters.Count)
                {
                    inspectedParameterIndex = -1;
                }

                var paramsCount = keyable.animationData.animatedParameters.Count;
                for (var i = 0; i < paramsCount; i++)
                {
                    var animParam = keyable.animationData.animatedParameters[i];
                    var paramRect = new Rect(expansionRect.xMin + 4, proposedHeight, expansionRect.width - 8, PARAMS_LINE_HEIGHT);
                    proposedHeight += PARAMS_LINE_HEIGHT + PARAMS_LINE_MARGIN;

                    GUI.color = inspectedParameterIndex == i ? new Color(0.5f, 0.5f, 1f, 0.4f) : new Color(0, 0.5f, 0.5f, 0.5f);
                    GUI.Box(paramRect, string.Empty, Styles.headerBoxStyle);
                    GUI.color = Color.white;

                    var paramName = string.Format(" <size=10><color=#252525>{0}</color></size>", animParam.ToString());
                    paramName = inspectedParameterIndex == i?string.Format("<b>{0}</b>", paramName) : paramName;

                    GUI.Label(paramRect, paramName, Styles.leftLabel);

                    var gearRect = new Rect(paramRect.xMax - 16 - 4, paramRect.y, 16, 16);
                    gearRect.center = new Vector2(gearRect.center.x, paramRect.y + (paramRect.height / 2) - 1);
                    GUI.enabled     = true;
                    GUI.color       = Color.white.WithAlpha(animParam.enabled ? 1 : 0.5f);
                    if (GUI.Button(gearRect, Styles.gearIcon, GUIStyle.none))
                    {
                        AnimatableParameterEditor.DoParamGearContextMenu(animParam, keyable);
                    }
                    GUI.enabled = animParam.enabled;
                    if (GUI.Button(paramRect, string.Empty, GUIStyle.none))
                    {
                        inspectedParameterIndex = inspectedParameterIndex == i ? -1 : i;
                        CurveEditor.FrameAllCurvesOf(animParam);
                    }
                    GUI.color   = Color.white;
                    GUI.enabled = true;
                }

                proposedHeight += PARAMS_TOP_MARGIN;

                if (inspectedParameterIndex >= 0)
                {
                    var controlRect = Rect.MinMaxRect(expansionRect.x + 6, proposedHeight + 5, expansionRect.xMax - 6, proposedHeight + 50);
                    var animParam   = keyable.animationData.animatedParameters[inspectedParameterIndex];
                    GUILayout.BeginArea(controlRect);
                    AnimatableParameterEditor.ShowMiniParameterKeyControls(animParam, keyable);
                    GUILayout.EndArea();
                    proposedHeight = controlRect.yMax + 10;
                }
            }

            if (showAddPropertyButton && inspectedParameterIndex == -1)
            {
                var buttonRect = Rect.MinMaxRect(expansionRect.x + 6, proposedHeight + 5, expansionRect.xMax - 6, proposedHeight + 25);
                var go         = keyable.animatedParametersTarget as GameObject;
                GUI.enabled = go != null && root.currentTime <= 0;
                if (GUI.Button(buttonRect, "Add Property"))
                {
                    EditorTools.ShowAnimatedPropertySelectionMenu(go, keyable.TryAddParameter);
                }
                GUI.enabled    = true;
                proposedHeight = buttonRect.yMax + 10;
            }

            //consume event
            if (e.type == EventType.MouseDown && expansionRect.Contains(e.mousePosition))
            {
                e.Use();
            }
        }
Exemple #3
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.RectangleHandleCap);
                        }
                    }
                    var cam = SceneView.lastActiveSceneView.camera;
                    Handles.RectangleHandleCap(0, pos, cam.transform.rotation, pointSize, EventType.Repaint);

                    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.CircleHandleCap);
                                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.CircleHandleCap);
                                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.SphereHandleCap(0, pos, Quaternion.identity, 0.02f, EventType.Repaint);
                        Handles.DrawLine(pos, nextPos);
                    }
                }
                Handles.color = Color.white;


                ///3. GUI
                if (kIndex >= 0)
                {
                    var guiRect = new Rect(Screen.width - 300, Screen.height - 190, 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);
                 *              }
                 */
            }
Exemple #4
0
        public override void OnGUI(Rect rect)
        {
            GUILayout.BeginVertical("box");

            GUI.color = new Color(0, 0, 0, 0.3f);
            GUILayout.BeginHorizontal(Slate.Styles.headerBoxStyle);
            GUI.color = Color.white;
            GUILayout.Label("<size=22><b>Global Editor Preferences</b></size>");
            GUILayout.EndHorizontal();
            GUILayout.Space(2);

            GUILayout.BeginVertical("box");
            Prefs.timeStepMode = (Prefs.TimeStepMode)EditorGUILayout.EnumPopup("Time Step Mode", Prefs.timeStepMode);
            if (Prefs.timeStepMode == Prefs.TimeStepMode.Seconds)
            {
                Prefs.snapInterval = EditorTools.CleanPopup <float>("Working Snap Interval", Prefs.snapInterval, Prefs.snapIntervals.ToList());
            }
            else
            {
                Prefs.frameRate = EditorTools.CleanPopup <int>("Working Frame Rate", Prefs.frameRate, Prefs.frameRates.ToList());
            }
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            Prefs.magnetSnapping             = EditorGUILayout.Toggle("Clips Magnet Snapping", Prefs.magnetSnapping);
            Prefs.lockHorizontalCurveEditing = EditorGUILayout.Toggle(new GUIContent("Lock xAxis Curve Editing", "Disallows moving keys in x in CurveEditor. They can still be moved in DopeSheetEditor"), Prefs.lockHorizontalCurveEditing);
            Prefs.autoFirstKey           = EditorGUILayout.Toggle(new GUIContent("Auto First Key", "If enabled, will automatically add a keyframe at time 0 of the animated property"), Prefs.autoFirstKey);
            Prefs.autoCleanKeysOffRange  = EditorGUILayout.Toggle(new GUIContent("Auto Clean Keys", "If enabled, will automatically clean keys off clip range"), Prefs.autoCleanKeysOffRange);
            Prefs.showDopesheetKeyValues = EditorGUILayout.Toggle("Show Keyframe Values", Prefs.showDopesheetKeyValues);
            Prefs.defaultTangentMode     = (TangentMode)EditorGUILayout.EnumPopup("Initial Keyframe Tangent", Prefs.defaultTangentMode);
            Prefs.keyframesStyle         = (Prefs.KeyframesStyle)EditorGUILayout.EnumPopup("Keyframes Style", Prefs.keyframesStyle);
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            Prefs.showShotThumbnails = EditorGUILayout.Toggle("Show Shot Thumbnails", Prefs.showShotThumbnails);
            if (Prefs.showShotThumbnails)
            {
                Prefs.thumbnailsRefreshInterval = EditorGUILayout.IntSlider(new GUIContent("Thumbnails Refresh", "The interval between which thumbnails refresh in editor frames"), Prefs.thumbnailsRefreshInterval, 2, 100);
            }
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            Prefs.scrollWheelZooms = EditorGUILayout.Toggle("Scroll Wheel Zooms", Prefs.scrollWheelZooms);
            Prefs.showDescriptions = EditorGUILayout.Toggle("Show Help Descriptions", Prefs.showDescriptions);
            Prefs.gizmosLightness  = EditorGUILayout.Slider("Gizmos Lightness", Prefs.gizmosLightness, 0, 1);
            Prefs.motionPathsColor = EditorGUILayout.ColorField("Motion Paths Color", Prefs.motionPathsColor);
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            GUI.enabled = !Application.isPlaying;
            var usePostStack    = DefinesManager.HasDefine(Prefs.USE_POSTSTACK_DEFINE);
            var newUsePostStack = EditorGUILayout.ToggleLeft(new GUIContent("Use Post Processing Stack V2 Define", "Enable this is you use Unity's PostProcessing Stack V2"), usePostStack);

            if (newUsePostStack != usePostStack)
            {
                if (newUsePostStack == true)
                {
                    EditorUtility.DisplayDialog("Post Processing Stack V2 Enabled", "Quick setup using Post Processing Stack 2 with Slate:\n\n1) Create a 'Global Post Process Volume' and set it to a new layer.\n\n2) Add a 'Post Processing Layer' Component to the 'Render Camera' and set the 'Layer' property to the one used in step 1.\n\n3) In the Director Camera inspector, set the 'Post Processing Volume Layer' to that same layer used in step 1 and 2.\n\nStep 3 is only Slate related step. For more information on step 1 and 2, please check the Post Processing Stack v2 documentation.", "OK");
                }
                DefinesManager.SetDefineActive(Prefs.USE_POSTSTACK_DEFINE, newUsePostStack);
            }

            //Needs more work
            // var useExpressions = DefinesManager.HasDefine(Prefs.USE_EXPRESSIONS_DEFINE);
            // var newUseExpressions = EditorGUILayout.ToggleLeft("Use Expressions Define (Very Experimental Feature!)", useExpressions);
            // if ( newUseExpressions != useExpressions ) {
            //     DefinesManager.SetDefineActive(Prefs.USE_EXPRESSIONS_DEFINE, newUseExpressions);
            // }

            GUI.enabled = true;
            GUILayout.EndVertical();


            GUI.backgroundColor = Prefs.autoKey ? new Color(1, 0.5f, 0.5f) : Color.white;
            if (GUILayout.Button(new GUIContent(Prefs.autoKey ? "AUTOKEY IS ENABLED" : "AUTOKEY IS DISABLED", Styles.keyIcon)))
            {
                Prefs.autoKey = !Prefs.autoKey;
            }
            GUI.backgroundColor = Color.white;


            GUILayout.EndVertical();

            if (firstPass || Event.current.type == EventType.Repaint)
            {
                firstPass     = false;
                myRect.height = GUILayoutUtility.GetLastRect().yMax + 5;
            }
        }
        public override void OnInspectorGUI()
        {
            GUILayout.Space(10);

            serializedObject.Update();
            EditorTools.Header("Head Look At");
            EditorGUILayout.PropertyField(neckProp);
            EditorGUILayout.PropertyField(headProp);
            EditorGUILayout.PropertyField(upVectorProp);
            EditorGUILayout.PropertyField(rotationOffsetProp);
            serializedObject.ApplyModifiedProperties();

            GUILayout.Space(10);

            EditorTools.Header("Expressions");
            var skins = character.GetComponentsInChildren <SkinnedMeshRenderer>().Where(s => s.sharedMesh.blendShapeCount > 0).ToList();

            if (skins == null || skins.Count == 0)
            {
                EditorGUILayout.HelpBox("There are no Skinned Mesh Renderers with blend shapes within the actor's GameObject hierarchy.", MessageType.Warning);
                return;
            }

            if (GUILayout.Button("Create New Expression"))
            {
                Undo.RecordObject(character, "Add Expression");
                character.expressions.Add(new BlendShapeGroup());
            }

            GUILayout.Space(5);


            EditorGUI.indentLevel++;
            foreach (var expression in character.expressions.ToArray())
            {
                var foldState = false;
                if (!foldStates.TryGetValue(expression, out foldState))
                {
                    foldStates[expression] = false;
                }

                GUI.backgroundColor = new Color(0, 0, 0, 0.3f);
                GUILayout.BeginVertical(Slate.Styles.headerBoxStyle);
                GUI.backgroundColor = Color.white;

                GUILayout.BeginHorizontal();
                foldStates[expression] = EditorGUILayout.Foldout(foldStates[expression], expression.name);
                if (GUILayout.Button("X", GUILayout.Width(18)))
                {
                    Undo.RecordObject(character, "Remove Expression");
                    expression.weight = 0;
                    character.expressions.Remove(expression);
                }
                GUILayout.EndHorizontal();

                if (foldStates[expression])
                {
                    EditorGUI.BeginChangeCheck();
                    var expName   = EditorGUILayout.TextField("Name", expression.name);
                    var expWeight = EditorGUILayout.Slider("Debug Weight", expression.weight, 0, 1);
                    if (EditorGUI.EndChangeCheck())
                    {
                        Undo.RecordObject(character, "Expression Changed");
                        expression.name   = expName;
                        expression.weight = expWeight;
                    }


                    foreach (var shape in expression.blendShapes.ToArray())
                    {
                        GUILayout.BeginHorizontal("box");

                        GUILayout.BeginVertical();
                        var skin   = shape.skin;
                        var name   = shape.name;
                        var weight = shape.weight;
                        EditorGUI.BeginChangeCheck();
                        skin = EditorTools.Popup <SkinnedMeshRenderer>("Skin", skin, skins);
                        if (skin != null)
                        {
                            name   = EditorTools.Popup <string>("Shape", name, skin.GetBlendShapeNames().ToList());
                            weight = EditorGUILayout.Slider("Weight", weight, 0, 1);
                        }
                        GUILayout.EndVertical();

                        if (skin != shape.skin || name != shape.name)
                        {
                            shape.SetRealWeight(0);
                        }

                        if (EditorGUI.EndChangeCheck())
                        {
                            Undo.RecordObject(character, "Expression Changed");
                            shape.skin   = skin;
                            shape.name   = name;
                            shape.weight = weight;
                        }

                        if (GUILayout.Button("X", GUILayout.Width(18), GUILayout.Height(50)))
                        {
                            Undo.RecordObject(character, "Remove Expression Blend Shape");
                            shape.SetRealWeight(0);
                            expression.blendShapes.Remove(shape);
                        }

                        GUILayout.EndHorizontal();
                        GUILayout.Space(5);
                    }

                    if (GUILayout.Button("Add Blend Shape"))
                    {
                        Undo.RecordObject(character, "Add Expression Blend Shape");
                        expression.blendShapes.Add(new BlendShape());
                    }

                    GUILayout.Space(5);
                }

                GUILayout.EndVertical();
                GUILayout.Space(5);
            }

            EditorGUI.indentLevel--;


            if (GUI.changed)
            {
                EditorUtility.SetDirty(character);
            }
        }
        public override void OnGUI(Rect rect)
        {
            GUILayout.BeginVertical("box");

            GUI.color = new Color(0, 0, 0, 0.3f);
            GUILayout.BeginHorizontal(Slate.Styles.headerBoxStyle);
            GUI.color = Color.white;
            GUILayout.Label("<size=22><b>Global Editor Preferences</b></size>");
            GUILayout.EndHorizontal();
            GUILayout.Space(2);

            GUILayout.BeginVertical("box");
            Prefs.timeStepMode = (Prefs.TimeStepMode)EditorGUILayout.EnumPopup("Time Step Mode", Prefs.timeStepMode);
            if (Prefs.timeStepMode == Prefs.TimeStepMode.Seconds)
            {
                Prefs.snapInterval = EditorTools.CleanPopup <float>("Working Snap Interval", Prefs.snapInterval, Prefs.snapIntervals.ToList());
            }
            else
            {
                Prefs.frameRate = EditorTools.CleanPopup <int>("Working Frame Rate", Prefs.frameRate, Prefs.frameRates.ToList());
            }
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            Prefs.magnetSnapping             = EditorGUILayout.Toggle("Clips Magnet Snapping", Prefs.magnetSnapping);
            Prefs.lockHorizontalCurveEditing = EditorGUILayout.Toggle(new GUIContent("Lock xAxis Curve Editing", "Disallows moving keys in x in CurveEditor. They can still be moved in DopeSheetEditor"), Prefs.lockHorizontalCurveEditing);
            Prefs.showDopesheetKeyValues     = EditorGUILayout.Toggle("Show Keyframe Values", Prefs.showDopesheetKeyValues);
            Prefs.defaultTangentMode         = (TangentMode)EditorGUILayout.EnumPopup("Initial Keyframe Tangent", Prefs.defaultTangentMode);
            Prefs.keyframesStyle             = (Prefs.KeyframesStyle)EditorGUILayout.EnumPopup("Keyframes Style", Prefs.keyframesStyle);
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            Prefs.showShotThumbnails = EditorGUILayout.Toggle("Show Shot Thumbnails", Prefs.showShotThumbnails);
            if (Prefs.showShotThumbnails)
            {
                Prefs.thumbnailsRefreshInterval = EditorGUILayout.IntSlider(new GUIContent("Thumbnails Refresh", "The interval between which thumbnails refresh in editor frames"), Prefs.thumbnailsRefreshInterval, 2, 100);
            }
            Prefs.showRuleOfThirds = EditorGUILayout.Toggle(new GUIContent("Show Rule Of Thirds"), Prefs.showRuleOfThirds);
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            Prefs.scrollWheelZooms = EditorGUILayout.Toggle("Scroll Wheel Zooms", Prefs.scrollWheelZooms);
            Prefs.showDescriptions = EditorGUILayout.Toggle("Show Help Descriptions", Prefs.showDescriptions);
            Prefs.gizmosLightness  = EditorGUILayout.Slider("Gizmos Lightness", Prefs.gizmosLightness, 0, 1);
            Prefs.motionPathsColor = EditorGUILayout.ColorField("Motion Paths Color", Prefs.motionPathsColor);
            GUILayout.EndVertical();

            GUILayout.BeginVertical("box");
            GUI.enabled = !Application.isPlaying;
            var usePostStack    = HasDefine(Prefs.USE_POSTSTACK_DEFINE);
            var newUsePostStack = EditorGUILayout.ToggleLeft(new GUIContent("Use Post Processing Stack Define", "Enable this is you use Unity's PostProcessing Stack"), usePostStack);

            if (newUsePostStack != usePostStack)
            {
                ToggleDefine(Prefs.USE_POSTSTACK_DEFINE, newUsePostStack);
            }

/*
 *                      var useExpressions = HasDefine(Prefs.USE_EXPRESSIONS_DEFINE);
 *                      var newUseExpressions = EditorGUILayout.ToggleLeft("Use Expressions Define (BETA)", useExpressions);
 *                      if (newUseExpressions != useExpressions){
 *                              ToggleDefine(Prefs.USE_EXPRESSIONS_DEFINE, newUseExpressions);
 *                      }
 */
            GUI.enabled = true;
            GUILayout.EndVertical();


            GUI.backgroundColor = Prefs.autoKey? new Color(1, 0.5f, 0.5f) : Color.white;
            if (GUILayout.Button(new GUIContent(Prefs.autoKey? "AUTOKEY IS ENABLED" : "AUTOKEY IS DISABLED", Styles.keyIcon)))
            {
                Prefs.autoKey = !Prefs.autoKey;
            }
            GUI.backgroundColor = Color.white;


            GUILayout.EndVertical();

            if (firstPass || Event.current.type == EventType.Repaint)
            {
                firstPass     = false;
                myRect.height = GUILayoutUtility.GetLastRect().yMax + 5;
            }
        }
Exemple #7
0
        public override void OnInspectorGUI()
        {
            base.ShowCommonInspector();

            serializedObject.Update();
            EditorGUILayout.PropertyField(blendInEffectProp);
            if (action.blendInEffect == CameraShot.BlendInEffectType.FadeFromColor)
            {
                EditorGUILayout.PropertyField(fadeFromColorProp);
            }
            EditorGUILayout.PropertyField(blendOutEffectProp);
            if (action.blendOutEffect == CameraShot.BlendOutEffectType.FadeToColor)
            {
                EditorGUILayout.PropertyField(fadeToColorProp);
            }
            EditorGUILayout.PropertyField(steadyCamEffectProp);
            serializedObject.ApplyModifiedProperties();

            if (action.parent.children.OfType <CameraShot>().FirstOrDefault() == action)
            {
                if (action.blendInEffect == CameraShot.BlendInEffectType.EaseIn)
                {
                    EditorGUILayout.HelpBox("The 'Ease In' option has no effect in the first shot clip of the track.\n\nIf you want to Blend in/out of the gameplay camera, please use the Blend In/Out found on the Camera Track inspector instead.", MessageType.Warning);
                }
                if (action.blendInEffect == CameraShot.BlendInEffectType.CrossDissolve)
                {
                    EditorGUILayout.HelpBox("The 'Cross Dissolve' option has no usable effect in the first shot clip of the track.", MessageType.Warning);
                }
            }

            if (action.targetShot == null)
            {
                EditorGUILayout.HelpBox("Select or Create a Shot Camera to be used by this clip.", MessageType.Error);
            }

            if (GUILayout.Button(action.targetShot == null ? "Select Shot" : "Replace Shot"))
            {
                if (action.targetShot == null || EditorUtility.DisplayDialog("Replace Shot", "Selecting a new target shot will reset all animation data of this clip.", "OK", "Cancel"))
                {
                    ShotPicker.Show(Event.current.mousePosition, action.root, (shot) => { action.targetShot = shot; });
                }
            }

            if (action.targetShot == null && GUILayout.Button("Create Shot"))
            {
                action.targetShot = ShotCamera.Create(action.root.context.transform);
            }

            if (action.targetShot != null)
            {
                if (GUILayout.Button("Find in Scene"))
                {
                    Selection.activeGameObject = action.targetShot.gameObject;
                }

                var lastRect = GUILayoutUtility.GetLastRect();
                var rect     = new Rect(lastRect.x, lastRect.yMax + 5, lastRect.width, 200);

                var res     = EditorTools.GetGameViewSize();
                var texture = action.targetShot.GetRenderTexture((int)res.x, (int)res.y);
                var style   = new GUIStyle("Box");
                style.alignment = TextAnchor.MiddleCenter;
                GUI.Box(rect, texture, style);

                if (action.targetShot.dynamicController.composer.trackingMode == DynamicCameraController.Composer.TrackingMode.FrameComposition)
                {
                    var scale       = Mathf.Min(rect.width / res.x, rect.height / res.y);
                    var result      = new Vector2((res.x * scale), (res.y * scale));
                    var boundedRect = new Rect(0, 0, result.x, result.y);
                    boundedRect.center = rect.center;
                    GUI.BeginGroup(boundedRect);
                    action.targetShot.dynamicController.DoGUI(action.targetShot, boundedRect);
                    GUI.EndGroup();
                }


                GUILayout.Space(205);

                var helpRect = new Rect(rect.x + 10, rect.yMax - 20, rect.width - 20, 16);
                GUI.color = EditorGUIUtility.isProSkin ? new Color(0, 0, 0, 0.6f) : new Color(1, 1, 1, 0.6f);
                GUI.DrawTexture(helpRect, Slate.Styles.whiteTexture);
                GUI.color = Color.white;
                GUI.Label(helpRect, "Left: Rotate, Middle: Pan, Right: Dolly, Alt+Right: Zoom");

                var e = Event.current;
                if (rect.Contains(e.mousePosition))
                {
                    EditorGUIUtility.AddCursorRect(rect, MouseCursor.Pan);
                    if (e.type == EventType.MouseDrag)
                    {
                        Undo.RecordObject(action.targetShot.transform, "Shot Change");
                        Undo.RecordObject(action.targetShot.cam, "Shot Change");
                        Undo.RecordObject(action.targetShot, "Shot Change");

                        var in2DMode = false;
                        var sc       = UnityEditor.SceneView.lastActiveSceneView;
                        if (sc != null)
                        {
                            in2DMode = sc.in2DMode;
                        }

                        //look
                        if (e.button == 0 && !in2DMode)
                        {
                            var deltaRot = new Vector3(e.delta.y, e.delta.x, 0) * 0.5f;
                            action.targetShot.localEulerAngles += deltaRot;
                            e.Use();
                        }
                        //pan
                        if (e.button == 2 || (e.button == 0 && in2DMode))
                        {
                            var deltaPos = new Vector3(-e.delta.x, e.delta.y, 0) * (e.shift ? 0.01f : 0.05f);
                            action.targetShot.transform.Translate(deltaPos);
                            e.Use();
                        }
                        //dolly in/out
                        if (e.button == 1 && !e.alt)
                        {
                            action.targetShot.transform.Translate(0, 0, e.delta.x * 0.05f);
                            e.Use();
                        }
                        //fov
                        if (e.button == 1 && e.alt)
                        {
                            action.fieldOfView -= e.delta.x;
                            e.Use();
                        }

                        EditorUtility.SetDirty(action.targetShot.transform);
                        EditorUtility.SetDirty(action.targetShot.cam);
                        EditorUtility.SetDirty(action.targetShot);
                    }
                }



                ///The shot dynamic controller settings
                if (shotSerializedObject == null || shotSerializedObject.targetObject != action.targetShot)
                {
                    if (action.targetShot != null)
                    {
                        shotSerializedObject = new SerializedObject(action.targetShot);
                        shotControllerProp   = shotSerializedObject.FindProperty("_dynamicController");
                    }
                }

                EditorGUI.BeginChangeCheck();
                if (shotSerializedObject != null)
                {
                    shotSerializedObject.Update();
                    EditorGUILayout.PropertyField(shotControllerProp);
                    shotSerializedObject.ApplyModifiedProperties();
                }
                if (EditorGUI.EndChangeCheck())
                {
                    action.Validate();
                }
                ///

                base.ShowAnimatableParameters();
            }
        }