// Animancer // Copyright 2020 Kybernetik // #if UNITY_EDITOR using System; using System.Reflection; using UnityEditor; using UnityEngine; namespace Animancer.Editor { /// [Editor-Only] Draws the Inspector GUI for an . public interface IAnimancerNodeDrawer { /// Draws the details and controls for the target node in the Inspector. void DoGUI(IAnimancerComponent owner); } /************************************************************************************************************************/ /// [Editor-Only] Draws the Inspector GUI for an . public abstract class AnimancerNodeDrawer : IAnimancerNodeDrawer where T : AnimancerNode { /************************************************************************************************************************/ /// The node being managed. public T Target { get; protected set; } /// If true, the details of the will be expanded in the Inspector. public bool IsExpanded { get { return Target._IsInspectorExpanded; } protected set { Target._IsInspectorExpanded = value; } } /************************************************************************************************************************/ /// The used for the area encompassing this drawer. protected abstract GUIStyle RegionStyle { get; } /************************************************************************************************************************/ /// Draws the details and controls for the target in the Inspector. public virtual void DoGUI(IAnimancerComponent owner) { if (!Target.IsValid) return; AnimancerGUI.BeginVerticalBox(RegionStyle); { DoHeaderGUI(); DoDetailsGUI(owner); } AnimancerGUI.EndVerticalBox(RegionStyle); CheckContextMenu(GUILayoutUtility.GetLastRect()); } /************************************************************************************************************************/ /// /// Draws the name and other details of the in the GUI. /// protected virtual void DoHeaderGUI() { var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before); DoLabelGUI(area); DoFoldoutGUI(area); } /// /// Draws a field for the if it has one, otherwise just a simple text /// label. /// protected abstract void DoLabelGUI(Rect area); /// Draws a foldout arrow to expand/collapse the node details. protected abstract void DoFoldoutGUI(Rect area); /// Draws the details of the in the GUI. protected abstract void DoDetailsGUI(IAnimancerComponent owner); /************************************************************************************************************************/ /// /// Draws controls for , , and /// . /// protected void DoNodeDetailsGUI() { var labelWidth = EditorGUIUtility.labelWidth; var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before); area.xMin += EditorGUI.indentLevel * AnimancerGUI.IndentSize; var indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; var right = area.xMax; // Is Playing. var state = Target as AnimancerState; if (state != null) { var label = AnimancerGUI.BeginTightLabel("Is Playing"); area.width = EditorGUIUtility.labelWidth + 16; state.IsPlaying = EditorGUI.Toggle(area, label, state.IsPlaying); AnimancerGUI.EndTightLabel(); area.x += area.width; area.xMax = right; } float speedWidth, weightWidth; Rect speedRect, weightRect; AnimancerGUI.SplitHorizontally(area, "Speed", "Weight", out speedWidth, out weightWidth, out speedRect, out weightRect); // Speed. EditorGUIUtility.labelWidth = speedWidth; EditorGUI.BeginChangeCheck(); var speed = EditorGUI.FloatField(speedRect, "Speed", Target.Speed); if (EditorGUI.EndChangeCheck()) Target.Speed = speed; if (AnimancerGUI.TryUseClickEvent(speedRect, 2)) Target.Speed = Target.Speed != 1 ? 1 : 0; // Weight. EditorGUIUtility.labelWidth = weightWidth; EditorGUI.BeginChangeCheck(); var weight = EditorGUI.FloatField(weightRect, "Weight", Target.Weight); if (EditorGUI.EndChangeCheck()) Target.Weight = weight; if (AnimancerGUI.TryUseClickEvent(weightRect, 2)) Target.Weight = Target.Weight != 1 ? 1 : 0; EditorGUI.indentLevel = indentLevel; EditorGUIUtility.labelWidth = labelWidth; DoFadeDetailsGUI(); } /************************************************************************************************************************/ /// /// Draws controls for and . /// private void DoFadeDetailsGUI() { var area = AnimancerGUI.LayoutSingleLineRect(AnimancerGUI.SpacingMode.Before); area = EditorGUI.IndentedRect(area); var speedLabel = AnimancerGUI.GetNarrowText("Fade Speed"); var targetLabel = AnimancerGUI.GetNarrowText("Target Weight"); float speedWidth, weightWidth; Rect speedRect, weightRect; AnimancerGUI.SplitHorizontally(area, speedLabel, targetLabel, out speedWidth, out weightWidth, out speedRect, out weightRect); var labelWidth = EditorGUIUtility.labelWidth; var indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; EditorGUI.BeginChangeCheck(); // Fade Speed. EditorGUIUtility.labelWidth = speedWidth; Target.FadeSpeed = EditorGUI.DelayedFloatField(speedRect, speedLabel, Target.FadeSpeed); if (AnimancerGUI.TryUseClickEvent(speedRect, 2)) { Target.FadeSpeed = Target.FadeSpeed != 0 ? 0 : Math.Abs(Target.Weight - Target.TargetWeight) / AnimancerPlayable.DefaultFadeDuration; } // Target Weight. EditorGUIUtility.labelWidth = weightWidth; Target.TargetWeight = EditorGUI.FloatField(weightRect, targetLabel, Target.TargetWeight); if (AnimancerGUI.TryUseClickEvent(weightRect, 2)) { if (Target.TargetWeight != Target.Weight) Target.TargetWeight = Target.Weight; else if (Target.TargetWeight != 1) Target.TargetWeight = 1; else Target.TargetWeight = 0; } if (EditorGUI.EndChangeCheck() && Target.FadeSpeed != 0) Target.StartFade(Target.TargetWeight, 1 / Target.FadeSpeed); EditorGUI.indentLevel = indentLevel; EditorGUIUtility.labelWidth = labelWidth; } /************************************************************************************************************************/ #region Context Menu /************************************************************************************************************************/ /// /// The menu label prefix used for details about the . /// protected const string DetailsPrefix = "Details/"; /// /// Checks if the current event is a context menu click within the `clickArea` and opens a context menu with various /// functions for the . /// protected void CheckContextMenu(Rect clickArea) { if (!AnimancerGUI.TryUseClickEvent(clickArea, 1)) return; var menu = new GenericMenu(); menu.AddDisabledItem(new GUIContent(Target.ToString())); PopulateContextMenu(menu); menu.AddItem(new GUIContent(DetailsPrefix + "Log Details"), false, () => Debug.Log(Target.GetDescription())); menu.AddItem(new GUIContent(DetailsPrefix + "Log Details Of Everything"), false, () => Debug.Log(Target.Root.GetDescription())); AddPlayableGraphVisualizerFunction(menu); menu.ShowAsContext(); } /// Adds functions relevant to the . protected abstract void PopulateContextMenu(GenericMenu menu); /************************************************************************************************************************/ private void AddPlayableGraphVisualizerFunction(GenericMenu menu) { var type = Type.GetType("GraphVisualizer.PlayableGraphVisualizerWindow," + " Unity.PlayableGraphVisualizer.Editor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); AnimancerEditorUtilities.AddMenuItem(menu, DetailsPrefix + "Playable Graph Visualizer", type != null, () => { var window = EditorWindow.GetWindow(type); var field = type.GetField("m_CurrentGraph", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); if (field != null) field.SetValue(window, Target.Root._Graph); }); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } } #endif