// 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