// Animancer // Copyright 2020 Kybernetik // #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value. #if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; namespace Animancer.Editor { /// [Editor-Only] A welcome screen for Animancer. /// Automatic selection is handled by ShowReadMeOnStartup. //[CreateAssetMenu(menuName = Strings.MenuItemPrefix + "Read Me", order = Strings.AssetMenuOrder + 4)] public sealed class ReadMe : ScriptableObject { /************************************************************************************************************************/ /// /// The release ID of this Animancer version. /// /// /// /// [1] = v1.0: 2018-05-02. /// [2] = v1.1: 2018-05-29. /// [3] = v1.2: 2018-08-14. /// [4] = v1.3: 2018-09-12. /// [5] = v2.0: 2018-10-08. /// [6] = v3.0: 2019-05-27. /// [7] = v3.1: 2019-08-12. /// [8] = v4.0: 2020-01-28. /// /// private const int ReleaseNumber = 8; /// The display name of this Animancer version. private const string VersionName = "v4.0"; /// The end of the URL for the change log of this Animancer version. private const string ChangeLogSuffix = "v4-0"; /// The key used to save the release number. private const string ReleaseNumberPrefKey = "Animancer.ReleaseNumber"; /************************************************************************************************************************/ [SerializeField] private Texture2D _Icon; [SerializeField] private DefaultAsset _ExamplesFolder; [SerializeField] private bool _DontShowOnStartup; /************************************************************************************************************************/ /// If true, ShowReadMeOnStartup will not automatically select this asset. public bool DontShowOnStartup { get { return _DontShowOnStartup && HasCorrectName; } } /************************************************************************************************************************/ /// /// The file name ends with the to detect if the user imported /// this version without deleting a previous version. /// /// When Unity's package importer sees an existing file with the same GUID as one in the package, it will /// overwrite that file but not move or rename it if the name has changed. So it will leave the file there with /// the old version name. /// private bool HasCorrectName { get { return name.EndsWith(VersionName); } } /************************************************************************************************************************/ [CustomEditor(typeof(ReadMe))] private sealed class Editor : UnityEditor.Editor { /************************************************************************************************************************/ /// The cast to . public ReadMe Target { get; private set; } /// The from when "Don't show this again" was last ticked. [NonSerialized] private int _PreviousVersion; /// The file path of the Examples folder. [NonSerialized] private string _ExamplesDirectory; /// The details of all example scenes. [NonSerialized] private List _Examples; /************************************************************************************************************************/ private void OnEnable() { Target = (ReadMe)target; _PreviousVersion = PlayerPrefs.GetInt(ReleaseNumberPrefKey, -1); if (_PreviousVersion < 0) _PreviousVersion = EditorPrefs.GetInt(ReleaseNumberPrefKey, -1);// Animancer v2.0 used EditorPrefs. _Examples = ExampleGroup.Gather(Target._ExamplesFolder, out _ExamplesDirectory); } /************************************************************************************************************************/ protected override void OnHeaderGUI() { GUILayout.BeginHorizontal("In BigTitle"); { const string Title = "Animancer Pro\n" + VersionName; var title = AnimancerGUI.TempContent(Title, null, false); var iconWidth = GUIStyles.Title.CalcHeight(title, EditorGUIUtility.currentViewWidth); GUILayout.Label(Target._Icon, GUILayout.Width(iconWidth), GUILayout.Height(iconWidth)); GUILayout.Label(title, GUIStyles.Title); } GUILayout.EndHorizontal(); } /************************************************************************************************************************/ public override void OnInspectorGUI() { DoWarnings(); DoShowOnStartup(); DoSpace(); GUILayout.BeginVertical(GUI.skin.box); DoHeadingLink("Documentation", null, Strings.DocumentationURL); DoSpace(); DoHeadingLink("Change Log", null, Strings.DocsURLs.ChangeLogPrefix + ChangeLogSuffix); GUILayout.EndVertical(); DoSpace(); GUILayout.BeginVertical(GUI.skin.box); DoHeadingLink("Examples", null, Strings.DocumentationURL + "/docs/examples"); if (Target._ExamplesFolder != null) { EditorGUILayout.ObjectField(_ExamplesDirectory, Target._ExamplesFolder, typeof(SceneAsset), false); ExampleGroup.DoExampleGUI(_Examples); } GUILayout.EndVertical(); DoSpace(); GUILayout.BeginVertical(GUI.skin.box); DoHeadingLink("Forum", "for general discussion, feedback, and news", "https://forum.unity.com/threads/566452"); DoSpace(); DoHeadingLink("Issues", "for questions, suggestions, and bug reports", "https://github.com/KybernetikGames/animancer/issues"); DoSpace(); DoHeadingLink("Email", "for anything private", "mailto:" + Strings.DeveloperEmail + "?subject=Animancer", Strings.DeveloperEmail); GUILayout.EndVertical(); DoSpace(); DoShowOnStartup(); } /************************************************************************************************************************/ private void DoShowOnStartup() { EditorGUI.BeginChangeCheck(); Target._DontShowOnStartup = GUILayout.Toggle(Target._DontShowOnStartup, "Don't show this Read Me on startup"); if (EditorGUI.EndChangeCheck()) { EditorUtility.SetDirty(Target); if (Target._DontShowOnStartup) PlayerPrefs.SetInt(ReleaseNumberPrefKey, ReleaseNumber); } } /************************************************************************************************************************/ private void DoSpace() { GUILayout.Space(AnimancerGUI.LineHeight * 0.2f); } /************************************************************************************************************************/ private void DoWarnings() { MessageType messageType; if (!Target.HasCorrectName) { messageType = MessageType.Error; } else if (_PreviousVersion >= 0 && _PreviousVersion < ReleaseNumber) { messageType = MessageType.Warning; } else return; // Upgraded from any older version. DoSpace(); var directory = AssetDatabase.GetAssetPath(Target); directory = Path.GetDirectoryName(directory); string versionWarning; if (messageType == MessageType.Error) { versionWarning = "You must fully delete any old version of Animancer before importing a new version." + "\n\nClick here to delete '" + directory + "' then you will need to import Animancer again."; } else { versionWarning = "You must fully delete any old version of Animancer before importing a new version." + "\n\nYou can ignore this message if you have already done so." + " Otherwise click here to delete '" + directory + "' then you will need to import Animancer again."; } EditorGUILayout.HelpBox(versionWarning, messageType); CheckDeleteAnimancer(directory); // Upgraded from before v2.0. if (_PreviousVersion < 4) { DoSpace(); EditorGUILayout.HelpBox("It seems you have just upgraded from an earlier version of Animancer" + " (before v2.0) so you will need to restart Unity before you can use it.", MessageType.Warning); } DoSpace(); } /************************************************************************************************************************/ /// /// Asks if the user wants to delete the root Animancer folder and does so if they confirm. /// private void CheckDeleteAnimancer(string directory) { if (!AnimancerGUI.TryUseClickEventInLastRect()) return; if (!AssetDatabase.IsValidFolder(directory)) { Debug.Log(directory + " doesn't exist." + " You must have moved Animancer somewhere else so you will need to delete it manually."); return; } if (!EditorUtility.DisplayDialog("Delete Animancer?", "Would you like to delete " + directory + "?" + "\n\nYou will then need to reimport Animancer manually.", "Delete", "Cancel")) return; AssetDatabase.DeleteAsset(directory); } /************************************************************************************************************************/ private bool DoHeadingLink(string heading, string description, string url, string displayURL = null) { if (DoLinkLabel(heading, description)) Application.OpenURL(url); bool clicked; if (displayURL == null) displayURL = url; var content = AnimancerGUI.TempContent(displayURL, "Click to copy this link to the clipboard", false); var area = AnimancerGUI.LayoutSingleLineRect(); if (GUI.Button(area, content, GUIStyles.URL)) { GUIUtility.systemCopyBuffer = displayURL; Debug.Log("Copied '" + displayURL + "' to the clipboard."); clicked = true; } else clicked = false; EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Text); return clicked; } /************************************************************************************************************************/ private bool DoLinkLabel(string label, string description) { var headingStyle = GUIStyles.Heading; var content = AnimancerGUI.TempContent(label, null, false); var size = headingStyle.CalcSize(content); var labelArea = GUILayoutUtility.GetRect(0, size.y); var buttonArea = AnimancerGUI.StealFromLeft(ref labelArea, size.x); if (description != null) GUI.Label(labelArea, description, GUIStyles.Description); Handles.BeginGUI(); Handles.color = headingStyle.normal.textColor; Handles.DrawLine(new Vector3(buttonArea.xMin, buttonArea.yMax), new Vector3(buttonArea.xMax, buttonArea.yMax)); Handles.color = Color.white; Handles.EndGUI(); EditorGUIUtility.AddCursorRect(buttonArea, MouseCursor.Link); return GUI.Button(buttonArea, content, headingStyle); } /************************************************************************************************************************/ private sealed class ExampleGroup { /************************************************************************************************************************/ /// The name of this group. public readonly string Name; /// The scenes in the . public readonly List Scenes = new List(); /// The folder paths of each of the . public readonly List Directories = new List(); /// Indicates whether this group should show its contents in the GUI. private bool _IsExpanded; /************************************************************************************************************************/ public static List Gather(DefaultAsset rootDirectoryAsset, out string examplesDirectory) { if (rootDirectoryAsset == null) { examplesDirectory = null; return null; } examplesDirectory = AssetDatabase.GetAssetPath(rootDirectoryAsset); if (string.IsNullOrEmpty(examplesDirectory)) return null; var directories = Directory.GetDirectories(examplesDirectory); var examples = new List(); List scenes = null; for (int i = 0; i < directories.Length; i++) { var directory = directories[i]; var files = Directory.GetFiles(directory, "*.unity", SearchOption.AllDirectories); for (int j = 0; j < files.Length; j++) { var scene = AssetDatabase.LoadAssetAtPath(files[j]); if (scene != null) { if (scenes == null) scenes = new List(); scenes.Add(scene); } } if (scenes != null) { examples.Add(new ExampleGroup(examplesDirectory, directory, scenes)); scenes = null; } } examplesDirectory = Path.GetDirectoryName(examplesDirectory); return examples; } /************************************************************************************************************************/ public ExampleGroup(string rootDirectory, string directory, List scenes) { var start = rootDirectory.Length + 1; Name = directory.Substring(start, directory.Length - start); Scenes = scenes; start = directory.Length + 1; for (int i = 0; i < scenes.Count; i++) { directory = AssetDatabase.GetAssetPath(scenes[i]); directory = directory.Substring(start, directory.Length - start); directory = Path.GetDirectoryName(directory); Directories.Add(directory); } } /************************************************************************************************************************/ public static void DoExampleGUI(List examples) { if (examples == null) return; for (int i = 0; i < examples.Count; i++) examples[i].DoExampleGUI(); } public void DoExampleGUI() { EditorGUI.indentLevel++; _IsExpanded = EditorGUILayout.Foldout(_IsExpanded, AnimancerGUI.TempContent(Name, null, false), true); if (_IsExpanded) { for (int i = 0; i < Scenes.Count; i++) { EditorGUI.indentLevel++; EditorGUILayout.ObjectField(Directories[i], Scenes[i], typeof(SceneAsset), false); EditorGUI.indentLevel--; } } EditorGUI.indentLevel--; } /************************************************************************************************************************/ } /************************************************************************************************************************/ private static class GUIStyles { /************************************************************************************************************************/ public static readonly GUIStyle Title = new GUIStyle(GUI.skin.label) { fontSize = 26, }; public static readonly GUIStyle Heading = new GUIStyle(GUI.skin.label) { fontSize = 18, stretchWidth = false, }; public static readonly GUIStyle Description = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.LowerLeft, }; public static readonly GUIStyle URL = new GUIStyle(GUI.skin.label); /************************************************************************************************************************/ static GUIStyles() { Heading.normal.textColor = Heading.hover.textColor = new Color32(0x00, 0x78, 0xDA, 0xFF); URL.fontSize = Mathf.CeilToInt(URL.fontSize * 0.8f); URL.normal.textColor = Color.Lerp(URL.normal.textColor, Color.grey, 0.75f); } /************************************************************************************************************************/ } /************************************************************************************************************************/ } /************************************************************************************************************************/ } } #endif