using System;
using System.IO;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using Needle.Engine.Utils;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
using UnityEngine.UIElements;
namespace Needle.Engine.Samples
{
[CreateAssetMenu(menuName = "Needle Engine/Samples/Sample Info")]
internal class SampleInfo : ScriptableObject
{
[UsedImplicitly]
public string Name
{
get => DisplayNameOrName;
set => DisplayName = value;
}
[Serializable]
public class ComponentList
{
public string Component;
public string Path;
public string MoreInfoUrl;
}
[JsonIgnore]
public string DisplayName;
public string Description;
public Texture2D Thumbnail;
[JsonIgnore]
public SceneAsset Scene;
public string LiveUrl;
public int Priority;
[JsonIgnore]
public string DisplayNameOrName => !string.IsNullOrWhiteSpace(DisplayName) ? DisplayName : ObjectNames.NicifyVariableName(name);
public Tag[] Tags;
[JsonIgnore][HideInInspector]
public SampleInfo reference;
public ComponentList[] UsedComponents;
///
/// Git directory relative path to the scene
///
[JsonProperty("ReadmeUrl")]
private string ReadmeUrl
{
get
{
if (!Scene) return "";
var sceneAssetPath = AssetDatabase.GetAssetPath(Scene);
if (sceneAssetPath == null)
{
Debug.LogError("Missing scene asset path...", this);
return "";
}
return Constants.RepoRoot + MakeGitDirectoryRelative(sceneAssetPath) + "/README.md";
}
}
private string MakeGitDirectoryRelative(string str)
{
var abs = Path.GetDirectoryName(Path.GetFullPath(str))?.Replace("\\", "/");
var dirInfo = new FileInfo(str).Directory;
var gitDirectory = dirInfo;
while (gitDirectory != null && gitDirectory.Exists)
{
var fileOrDir = gitDirectory.GetFileSystemInfos(".git");
if (fileOrDir.Length > 0)
{
break;
}
gitDirectory = gitDirectory.Parent;
}
var gitPath = gitDirectory?.FullName.Replace("\\", "/");
var gitRelative = abs.Substring(gitPath!.Length);
while (gitRelative.StartsWith("/"))
gitRelative = gitRelative.Substring(1);
return gitRelative;
}
private void OnValidate()
{
if (!Scene)
{
var path = AssetDatabase.GetAssetPath(this);
if (string.IsNullOrWhiteSpace(path)) return;
var scenes = AssetDatabase.FindAssets("t:SceneAsset", new[] { Path.GetDirectoryName(path) });
foreach (var guid in scenes)
{
var scene = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid));
Scene = scene;
if (scene)
break;
}
}
}
#if UNITY_EDITOR
[OnOpenAsset(100)]
private static bool OpenAsset(int instanceID, int line)
{
// Handle SampleInfo assets
if (EditorUtility.InstanceIDToObject(instanceID) is SampleInfo sampleInfo)
{
SamplesWindow.OpenScene(sampleInfo.Scene);
return true;
}
// Handle scenes that are part of a sample / immutable
if (EditorUtility.InstanceIDToObject(instanceID) is SceneAsset sceneAsset)
{
var scenePath = AssetDatabase.GetAssetPath(sceneAsset);
if (PackageUtils.IsMutable(scenePath))
return false;
SamplesWindow.OpenScene(sceneAsset);
return true;
}
return false;
}
#endif
public override string ToString()
{
return DisplayNameOrName + " – " + name;
}
#if UNITY_EDITOR
[ContextMenu("Open local Readme")]
void OpenReadme()
{
var readmeFile = Path.GetDirectoryName(AssetDatabase.GetAssetPath(Scene)) + "/README.md";
if (!File.Exists(readmeFile)) return;
AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath(readmeFile));
}
#endif
}
#if UNITY_EDITOR
[CustomEditor(typeof(SampleInfo), true)]
[CanEditMultipleObjects]
class SampleInfoEditor : Editor
{
public override VisualElement CreateInspectorGUI()
{
// if we have multiple sample assets selected just use the default inspector (for editing multiple tags at once)
if (targets.Length > 1) return null;
var t = target as SampleInfo;
if (!t) return new Label("");
var isSubAsset = AssetDatabase.IsSubAsset(t);
var v = new VisualElement();
foreach (var style in SamplesWindow.StyleSheet)
v.styleSheets.Add(style);
if (!EditorGUIUtility.isProSkin) v.AddToClassList("__light");
var sample = new SamplesWindow.Sample(t);
sample.style.marginLeft = -10;
sample.style.marginRight = -10;
v.Add(sample);
var readmeFile = Path.GetDirectoryName(AssetDatabase.GetAssetPath(t.Scene)) + "/README.md";
var readmeText = File.ReadAllText(readmeFile);
var readmeRichText = ConvertMarkdownToRichText(readmeText);
GUIStyle _readmeStyle = null;
var label = new IMGUIContainer(() =>
{
if (_readmeStyle == null)
{
_readmeStyle= new GUIStyle(GUI.skin.label);
_readmeStyle.wordWrap = true;
_readmeStyle.richText = true;
_readmeStyle.fixedHeight = 0;
_readmeStyle.stretchHeight = true;
}
// Links are not clickable here for some reason...
// with SelectableLabel they are, but then the layout is messed up.
var content = new GUIContent(readmeRichText);
GUILayout.Label(content.text, _readmeStyle);
});
if (!isSubAsset)
{
var container = new IMGUIContainer(() => DrawDefaultInspector());
container.style.marginTop = 20;
v.Add(container);
// Show the readme text
v.Add(label);
}
else
v.style.maxHeight = 500;
return v;
}
// Copied from Readme.cs (different packages)
public static string ConvertMarkdownToRichText(string markdown)
{
// Convert headers
markdown = Regex.Replace(markdown, @"^#\s+(.+)$", "$1", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^##\s+(.+)$", "$1", RegexOptions.Multiline);
markdown = Regex.Replace(markdown, @"^###\s+(.+)$", "$1", RegexOptions.Multiline);
// Convert bold
markdown = Regex.Replace(markdown, @"\*\*(.+?)\*\*", "$1");
markdown = Regex.Replace(markdown, @"__(.+?)__", "$1");
// Convert italic
markdown = Regex.Replace(markdown, @"\*(.+?)\*", "$1");
//markdown = Regex.Replace(markdown, @"_(.+?)_", "$1"); // fails?
// Convert block
markdown = Regex.Replace(markdown, @"```([^`]+)```", "\n-----$1-----\n");
// Convert inline-block
markdown = Regex.Replace(markdown, @"\`(.+?)\`", "$1");
// Convert lists (assuming unordered lists)
markdown = Regex.Replace(markdown, @"^\s*-\s+(.+)$", "• $1", RegexOptions.Multiline);
// Convert links
markdown = Regex.Replace(markdown, @"\[([^\]]+)\]\(([^\)]+)\)","$1");
// Replace sequence of at least three --- with a horizontal line
markdown = Regex.Replace(markdown, @"-{3,}", "––––––––––");
return markdown;
}
}
#endif
}