329 lines
12 KiB
C#
329 lines
12 KiB
C#
// 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
|
|
{
|
|
/// <summary>[Editor-Only] A component which explains a sample scene.</summary>
|
|
/// 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;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Custom editor for <see cref="SampleReadMe"/>.</summary>
|
|
[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");
|
|
|
|
/// <inheritdoc/>
|
|
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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns an error message about a missing Unity module.</summary>
|
|
[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
|
|
/// <summary>An error message about the 'Audio' module being missing.</summary>
|
|
[HideInCallstack]
|
|
public static void LogMissingAudioModuleError(Object context)
|
|
=> LogMissingModuleError(
|
|
"Audio",
|
|
"https://docs.unity3d.com/ScriptReference/UnityEngine.AudioModule.html",
|
|
context);
|
|
#endif
|
|
|
|
#if !UNITY_JSON_SERIALIZE
|
|
/// <summary>An error message about the 'JSON Serialize' module being missing.</summary>
|
|
[HideInCallstack]
|
|
public static void LogMissingJsonSerializeModuleError(Object context)
|
|
=> LogMissingModuleError(
|
|
"JSON Serialize",
|
|
"https://docs.unity3d.com/ScriptReference/UnityEngine.JSONSerializeModule.html",
|
|
context);
|
|
#endif
|
|
|
|
#if !UNITY_PHYSICS_3D
|
|
/// <summary>An error message about the 'Physics' module being missing.</summary>
|
|
[HideInCallstack]
|
|
public static void LogMissingPhysics3DModuleError(Object context)
|
|
=> LogMissingModuleError(
|
|
"Physics",
|
|
"https://docs.unity3d.com/ScriptReference/UnityEngine.PhysicsModule.html",
|
|
context);
|
|
#endif
|
|
|
|
#if !UNITY_UGUI
|
|
/// <summary>An error message about the 'Unity UI' module being missing.</summary>
|
|
[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
|
|
|