// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Animancer.Samples
{
/// [Editor-Only] A component which explains a sample scene.
/// https://kybernetik.com.au/animancer/api/Animancer.Samples/SampleReadMe
[AnimancerHelpUrl(typeof(SampleReadMe))]
[AddComponentMenu("")]
public class SampleReadMe : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField, Multiline(10)]
private string _Text;
/************************************************************************************************************************/
#if UNITY_IMGUI
/************************************************************************************************************************/
[InitializeOnLoadMethod]
private static void InitializeAutoSelect()
{
bool isInEditMode = !EditorApplication.isPlayingOrWillChangePlaymode;
EditorApplication.playModeStateChanged += change =>
{
switch (change)
{
case PlayModeStateChange.EnteredEditMode:
isInEditMode = true;
break;
default:
isInEditMode = false;
break;
}
};
EditorSceneManager.activeSceneChangedInEditMode += (from, to) =>
{
if (!isInEditMode)
return;
Object selected = Selection.activeObject;
if (selected != null && selected is not SceneAsset)
return;
foreach (GameObject gameObject in to.GetRootGameObjects())
{
if (gameObject.TryGetComponent(out SampleReadMe instance))
{
EditorApplication.delayCall += () =>
{
Selection.activeObject = instance.gameObject;
InternalEditorUtility.SetIsInspectorExpanded(instance, true);
};
break;
}
}
};
}
/************************************************************************************************************************/
/// Custom editor for .
[CustomEditor(typeof(SampleReadMe))]
public class Editor : UnityEditor.Editor
{
/************************************************************************************************************************/
private static GUIStyle _HeaderStyle;
private static GUIStyle _BodyStyle;
private static GUIStyle _FooterStyle;
private static void InitializeStyles()
{
if (_HeaderStyle != null)
return;
GUIStyle label = GUI.skin.label;
_HeaderStyle = new(label)
{
stretchWidth = false,
};
_HeaderStyle.fontSize *= 2;
_HeaderStyle.normal.textColor = _HeaderStyle.hover.textColor =
new Color32(0x00, 0x78, 0xDA, 0xFF);
_BodyStyle = new(label)
{
richText = true,
wordWrap = true,
};
_FooterStyle = new(label)
{
fontStyle = FontStyle.Italic,
};
_FooterStyle.fontSize = (int)(_FooterStyle.fontSize * 0.75f);
}
/************************************************************************************************************************/
private static readonly GUIContent UrlContent = new("", "Click to copy this link to the clipboard");
///
public override void OnInspectorGUI()
{
InitializeStyles();
GUILayout.BeginVertical();
SampleReadMe target = (SampleReadMe)this.target;
Scene scene = target.gameObject.scene;
if (!scene.IsValid())
{
GUILayout.Label("This component only works inside scenes.", _BodyStyle);
return;
}
string url = GetDocumentationURL(scene.path);
// Header.
string number = GetSampleNumber(scene);
if (!string.IsNullOrEmpty(number))
GUILayout.Label(number, _FooterStyle);
GUILayout.Label(scene.name, _HeaderStyle);
Rect headerArea = GUILayoutUtility.GetLastRect();
headerArea.y += headerArea.height;
headerArea.height *= 0.05f;
EditorGUI.DrawRect(headerArea, _HeaderStyle.normal.textColor);
// URL.
UrlContent.text = url;
if (GUILayout.Button(UrlContent, _FooterStyle))
{
GUIUtility.systemCopyBuffer = url;
Debug.Log($"Copied '{url}' to the clipboard.");
}
Rect urlArea = GUILayoutUtility.GetLastRect();
EditorGUIUtility.AddCursorRect(urlArea, MouseCursor.Text);
GUILayout.Space(_BodyStyle.fontSize);
// Body.
if (!string.IsNullOrEmpty(target._Text))
{
GUILayout.Label(target._Text, _BodyStyle);
GUILayout.Space(_BodyStyle.fontSize);
}
// Footer.
GUILayout.Label("Click here to open the detailed online documentation.", _FooterStyle);
GUILayout.EndVertical();
// Click to open URL.
Rect area = GUILayoutUtility.GetLastRect();
EditorGUIUtility.AddCursorRect(area, MouseCursor.Link);
Event currentEvent = Event.current;
if (currentEvent.type == EventType.MouseUp &&
area.Contains(currentEvent.mousePosition))
Application.OpenURL(url);
//base.OnInspectorGUI();// Uncomment to allow editing.
}
/************************************************************************************************************************/
[NonSerialized]
private string _SampleNumber;
private string GetSampleNumber(Scene scene)
{
if (_SampleNumber is not null)
return _SampleNumber;
try
{
string directory = Path.GetDirectoryName(scene.path);
string name = Path.GetFileName(directory);
int space = name.IndexOf(' ');
_SampleNumber = name[..space];
directory = Path.GetDirectoryName(directory);
name = Path.GetFileName(directory);
space = name.IndexOf(' ');
_SampleNumber = $"{name[..space]}-{_SampleNumber}";
}
catch (Exception exception)
{
_SampleNumber = "";
_DocumentationURL = $"Failed to get Sample Number for {scene.path}: {exception}";
}
return _SampleNumber;
}
/************************************************************************************************************************/
[NonSerialized]
private string _DocumentationURL;
private string GetDocumentationURL(string scenePath)
{
if (_DocumentationURL is not null)
return _DocumentationURL;
try
{
scenePath = Path.GetDirectoryName(scenePath);
string urlPath = Path.Combine(scenePath, "Documentation.URL");
string urlText = File.ReadAllText(urlPath);
const string Prefix = "URL=";
int start = urlText.IndexOf(Prefix) + Prefix.Length;
int end = urlText.IndexOf('\n', start);
_DocumentationURL = urlText[start..end];
}
catch (Exception exception)
{
_DocumentationURL = $"Failed to get Documentation URL for {scenePath}: {exception}";
}
return _DocumentationURL;
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
#region Unity Modules
/************************************************************************************************************************/
/// Returns an error message about a missing Unity module.
[HideInCallstack]
public static void LogMissingModuleError(string name, string url, Object context)
=> Debug.LogError(
$"{context.GetType().Name} requires Unity's '{name}'" +
$" module to be enabled in the Package Manager. {url}",
context);
#if !UNITY_AUDIO
/// An error message about the 'Audio' module being missing.
[HideInCallstack]
public static void LogMissingAudioModuleError(Object context)
=> LogMissingModuleError(
"Audio",
"https://docs.unity3d.com/ScriptReference/UnityEngine.AudioModule.html",
context);
#endif
#if !UNITY_JSON_SERIALIZE
/// An error message about the 'JSON Serialize' module being missing.
[HideInCallstack]
public static void LogMissingJsonSerializeModuleError(Object context)
=> LogMissingModuleError(
"JSON Serialize",
"https://docs.unity3d.com/ScriptReference/UnityEngine.JSONSerializeModule.html",
context);
#endif
#if !UNITY_PHYSICS_3D
/// An error message about the 'Physics' module being missing.
[HideInCallstack]
public static void LogMissingPhysics3DModuleError(Object context)
=> LogMissingModuleError(
"Physics",
"https://docs.unity3d.com/ScriptReference/UnityEngine.PhysicsModule.html",
context);
#endif
#if !UNITY_UGUI
/// An error message about the 'Unity UI' module being missing.
[HideInCallstack]
public static void LogMissingUnityUIModuleError(Object context)
=> LogMissingModuleError(
"Unity UGUI",
"https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/index.html",
context);
#endif
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}
#endif