// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.GraphicsTools.Editor
{
///
/// General utility methods to help with Graphics Tools development.
///
public static class DevelopmentUtilities
{
public static readonly string PackageName = "com.microsoft.mrtk.graphicstools.unity";
private static bool isInitialized = false;
private static bool isPackageMutable = false;
private static readonly string visibleSamplesPath = "../../com.microsoft.mrtk.graphicstools.unity/Runtime/Samples";
private static readonly string hiddenSamplesPath = "../../com.microsoft.mrtk.graphicstools.unity/Samples~";
///
/// Performs one time initialization.
///
private static void Initialize()
{
if (!isInitialized)
{
isPackageMutable = !InstalledInProject(PackageName) ||
InstalledInProjectAsLocalFolder(PackageName);
isInitialized = true;
}
}
///
/// Returns true if the Graphics Tools package can be altered.
///
public static bool IsPackageMutable()
{
Initialize();
return isPackageMutable;
}
///
/// Automatically shows the samples folder if currently hidden.
///
[MenuItem("Window/Graphics Tools/Show Samples")]
public static void ShowSamples()
{
try
{
string hiddenPath = GetFullPath(hiddenSamplesPath);
if (Directory.Exists(hiddenPath))
{
string visiblePath = GetFullPath(visibleSamplesPath);
if (Directory.Exists(visiblePath))
{
if (!IsDirectoryEmpty(visiblePath))
{
if (EditorUtility.DisplayDialog("Overwrite?",
string.Format("\"{0}\" contains files. Would you like to overwrite them?", visiblePath),
"Delete",
"Cancel"))
{
Directory.Delete(visiblePath, true);
}
else
{
return;
}
}
else
{
Directory.Delete(visiblePath);
}
}
Directory.Move(hiddenPath, visiblePath);
AssetDatabase.Refresh();
}
}
catch (Exception e)
{
Debug.LogException(e);
EditorUtility.DisplayDialog("Failed to show samples",
$"Showing samples failed.\n\nTry closing apps or windows that are currently using files within the \"Samples~\" folder.\n\nException: {e.Message}",
"Ok");
}
}
///
/// Menu item validation.
///
[MenuItem("Window/Graphics Tools/Show Samples", true)]
public static bool ValidateShowSamples()
{
if (!IsPackageMutable())
{
return false;
}
string path = GetFullPath(hiddenSamplesPath);
return Directory.Exists(path) && !IsDirectoryEmpty(path);
}
///
/// Automatically hides the samples folder if currently visible.
///
[MenuItem("Window/Graphics Tools/Hide Samples")]
public static void HideSamples()
{
try
{
string visiblePath = GetFullPath(visibleSamplesPath);
if (Directory.Exists(visiblePath))
{
string hiddenPath = GetFullPath(hiddenSamplesPath);
if (Directory.Exists(hiddenPath))
{
if (!IsDirectoryEmpty(hiddenPath))
{
if (EditorUtility.DisplayDialog("Overwrite?",
string.Format("\"{0}\" contains files. Would you like to overwrite them?", hiddenPath),
"Delete",
"Cancel"))
{
Directory.Delete(hiddenPath, true);
}
else
{
return;
}
}
else
{
Directory.Delete(hiddenPath);
}
}
Directory.Move(visiblePath, hiddenPath);
File.Delete(visiblePath + ".meta"); // Remove the lingering meta files as well.
AssetDatabase.Refresh();
}
}
catch (Exception e)
{
Debug.LogException(e);
EditorUtility.DisplayDialog("Failed to hide samples",
$"Hiding samples failed.\n\nTry closing apps or windows that are currently using files within the \"Samples\" folder.\n\nException: {e.Message}",
"Ok");
}
}
///
/// Menu item validation.
///
[MenuItem("Window/Graphics Tools/Hide Samples", true)]
public static bool ValidateHideSamples()
{
if (!IsPackageMutable())
{
return false;
}
string path = GetFullPath(visibleSamplesPath);
return Directory.Exists(path) && !IsDirectoryEmpty(path);
}
///
/// Generates a material for Graphics Tools Standard shader..
///
[MenuItem("Assets/Create/Graphics Tools/Material", false, 1)]
public static void CreateGraphicsToolsMaterial()
{
var directory = AssetDatabase.GetAssetPath(Selection.activeObject);
Material material = new Material(StandardShaderUtility.GraphicsToolsStandardShader);
if (string.IsNullOrEmpty(directory))
{
directory = "Assets";
}
var extension = Path.GetExtension(directory);
if (!string.IsNullOrEmpty(extension))
{
var filename = Path.GetFileName(directory);
var startIndex = directory.LastIndexOf(filename) - 1;
var count = filename.Length + 1;
directory = directory.Remove(startIndex, count);
}
var path = directory + "/" + "NewGraphicsToolsMaterial.mat";
var uniquePath = AssetDatabase.GenerateUniqueAssetPath(path);
AssetDatabase.CreateAsset(material, uniquePath);
AssetDatabase.SaveAssets();
Selection.activeObject = material;
}
// Only show these menu items in the URP since built-in and HDRP have their own mipmap debug visualizers.
#if GT_USE_URP
///
/// Enables a debug draw mode in all scene views which tints pixels based on their texel to pixel ratio.
/// - Original color means it's a perfect match (1:1 texels to pixels ratio at the current distance and resolution).
/// - Red indicates that the texture is larger than necessary.
/// - Blue indicates that the texture could be larger.
/// Note, the ideal texture sizes depend on the resolution at which your application will run and how close the camera can get to a surface.
///
[MenuItem("Window/Graphics Tools/Draw Modes/Mipmaps - Enable")]
public static void EnableDrawModeMipmaps()
{
Shader debugShader = Shader.Find("Hidden/Graphics Tools/DebugMipColor");
if (debugShader != null)
{
foreach (SceneView sceneView in SceneView.sceneViews)
{
sceneView.SetSceneViewShaderReplace(debugShader, null);
}
SceneView.RepaintAll();
}
else
{
Debug.LogWarning("Failed to find \"Hidden/Graphics Tools/DebugMipColor\" shader.");
}
}
///
/// Disables the mipmap debug draw mode in all scene views. (See EnableDrawModeMipMaps for more info.)
///
[MenuItem("Window/Graphics Tools/Draw Modes/Mipmaps - Disable")]
public static void DisableDrawModeMipmaps()
{
foreach (SceneView sceneView in SceneView.sceneViews)
{
sceneView.SetSceneViewShaderReplace(null, null);
}
SceneView.RepaintAll();
}
#endif // GT_USE_URP
///
/// Returns true if the package id is found in the manifest.json file.
///
private static bool InstalledInProject(string packageId)
{
try
{
string manifest = File.ReadAllText("Packages/manifest.json");
return manifest.Contains(packageId);
}
catch (Exception e)
{
Debug.LogException(e);
}
return false;
}
///
/// Returns true if the package id is found in the manifest.json file and is installed locally as a folder.
///
private static bool InstalledInProjectAsLocalFolder(string packageId)
{
try
{
foreach (string line in File.ReadLines("Packages/manifest.json"))
{
if (line.Contains(packageId) && line.Contains("file:") && !line.Contains(".tgz"))
{
return true;
}
}
}
catch (Exception e)
{
Debug.LogException(e);
}
return false;
}
///
/// Returns the full path of a directory relative to the Assets folder.
///
private static string GetFullPath(string localPath)
{
return Path.GetFullPath(Path.Combine(Application.dataPath, localPath));
}
///
/// Returns true if a directory has nothing in it.
///
private static bool IsDirectoryEmpty(string path)
{
return !Directory.EnumerateFileSystemEntries(path).Any();
}
}
}