EditorUtils.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /**
  2. * Copyright (c) 2021 Vuplex Inc. All rights reserved.
  3. *
  4. * Licensed under the Vuplex Commercial Software Library License, you may
  5. * not use this file except in compliance with the License. You may obtain
  6. * a copy of the License at
  7. *
  8. * https://vuplex.com/commercial-library-license
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #pragma warning disable CS0618
  17. using System;
  18. using System.IO;
  19. using System.Linq;
  20. using UnityEngine;
  21. using UnityEditor;
  22. using UnityEditor.Build;
  23. namespace Vuplex.WebView {
  24. static class EditorUtils {
  25. public static void AssertThatOculusLowOverheadModeIsDisabled() {
  26. if (!EditorUtils.XrSdkIsEnabled("oculus")) {
  27. return;
  28. }
  29. var lowOverheadModeEnabled = false;
  30. #if VUPLEX_OCULUS
  31. // The Oculus XR plugin is installed
  32. Unity.XR.Oculus.OculusLoader oculusLoader = Unity.XR.Oculus.OculusSettings.CreateInstance<Unity.XR.Oculus.OculusLoader>();
  33. Unity.XR.Oculus.OculusSettings oculusSettings = oculusLoader.GetSettings();
  34. lowOverheadModeEnabled = oculusSettings.LowOverheadMode;
  35. #elif UNITY_2019_2_OR_NEWER && !UNITY_2020_1_OR_NEWER
  36. // VROculus.lowOverheadMode is only supported from Unity 2019.2 - 2019.4
  37. lowOverheadModeEnabled = PlayerSettings.VROculus.lowOverheadMode;
  38. #endif
  39. if (lowOverheadModeEnabled) {
  40. throw new BuildFailedException("XR settings error: Vuplex 3D WebView requires that \"Low Overhead Mode\" be disabled in Oculus XR settings. Please disable Low Overhead Mode in Oculus XR settings.");
  41. }
  42. }
  43. public static void AssertThatSrpBatcherIsDisabled() {
  44. #if UNITY_2018_2_OR_NEWER && !VUPLEX_DISABLE_SRP_WARNING
  45. if (UnityEngine.Rendering.GraphicsSettings.useScriptableRenderPipelineBatching) {
  46. throw new BuildFailedException("URP settings error: \"SRP Batcher\" is enabled in Universal Render Pipeline (URP) settings, but URP for Android has an issue that prevents 3D WebView's textures from showing up outside of a Canvas. If the project uses a WebViewPrefab, please go to \"UniversalRenderPipelineAsset\" -> \"Advanced\" and disable SRP Batcher. If the project only uses CanvasWebViewPrefab and not WebViewPrefab, you can instead add the scripting symbol VUPLEX_DISABLE_SRP_WARNING to the project to ignore this warning.");
  47. }
  48. #endif
  49. }
  50. public static void CopyAndReplaceDirectory(string srcPath, string dstPath, bool ignoreMetaFiles = true) {
  51. if (Directory.Exists(dstPath)) {
  52. Directory.Delete(dstPath, true);
  53. }
  54. if (File.Exists(dstPath)) {
  55. File.Delete(dstPath);
  56. }
  57. Directory.CreateDirectory(dstPath);
  58. foreach (var file in Directory.GetFiles(srcPath)) {
  59. if (!ignoreMetaFiles || Path.GetExtension(file) != ".meta") {
  60. File.Copy(file, Path.Combine(dstPath, Path.GetFileName(file)));
  61. }
  62. }
  63. foreach (var dir in Directory.GetDirectories(srcPath)) {
  64. CopyAndReplaceDirectory(dir, Path.Combine(dstPath, Path.GetFileName(dir)), ignoreMetaFiles);
  65. }
  66. }
  67. public static void DrawLink(string linkText, string url, int underlineLength) {
  68. var linkStyle = new GUIStyle {
  69. richText = true,
  70. padding = new RectOffset {
  71. top = 10,
  72. bottom = 10
  73. }
  74. };
  75. var linkClicked = GUILayout.Button(
  76. EditorUtils.TextWithColor(linkText, EditorUtils.GetLinkColor()),
  77. linkStyle
  78. );
  79. var linkRect = GUILayoutUtility.GetLastRect();
  80. EditorGUIUtility.AddCursorRect(linkRect, MouseCursor.Link);
  81. // Unity's editor GUI doesn't support underlines, so fake it.
  82. var underscores = new string[underlineLength];
  83. for (var i = 0; i < underlineLength; i++) {
  84. underscores[i] = "_";
  85. }
  86. var underline = String.Join("", underscores);
  87. GUI.Label(
  88. linkRect,
  89. EditorUtils.TextWithColor(underline, EditorUtils.GetLinkColor()),
  90. new GUIStyle {
  91. richText = true,
  92. padding = new RectOffset {
  93. top = 12,
  94. bottom = 10
  95. }
  96. });
  97. if (linkClicked) {
  98. Application.OpenURL(url);
  99. }
  100. }
  101. /// <summary>
  102. /// Returns the path to a given directory, searching for it if needed.
  103. /// If `directoryToSearch` isn't provided, `Application.dataPath` is used.
  104. /// </summary>
  105. public static string FindDirectory(string expectedPath, string directoryToSearch = null, string[] ignorePaths = null) {
  106. if (Directory.Exists(expectedPath)) {
  107. return expectedPath;
  108. }
  109. // The directory isn't in the expected location, so fall back to finding it.
  110. var directoryName = Path.GetFileName(expectedPath);
  111. if (directoryToSearch == null) {
  112. directoryToSearch = Application.dataPath;
  113. }
  114. var directories = Directory.GetDirectories(directoryToSearch, directoryName, SearchOption.AllDirectories);
  115. if (ignorePaths != null) {
  116. directories = directories.ToList().Where(d => !ignorePaths.Contains(d)).ToArray();
  117. }
  118. if (directories.Length == 1) {
  119. return directories[0];
  120. }
  121. var errorMessage = String.Format("Unable to locate the directory {0}. It's not in the expected location ({1}), and {2} instances of the directory were found in the directory {3}.", directoryName, expectedPath, directories.Length, directoryToSearch);
  122. throw new Exception(errorMessage);
  123. }
  124. /// <summary>
  125. /// Returns the path to a given file, searching for it if needed.
  126. /// If `directoryToSearch` isn't provided, `Application.dataPath` is used.
  127. /// </summary>
  128. public static string FindFile(string expectedPath, string directoryToSearch = null) {
  129. if (File.Exists(expectedPath)) {
  130. return expectedPath;
  131. }
  132. // The file isn't in the expected location, so fall back to finding it.
  133. var fileName = Path.GetFileName(expectedPath);
  134. if (directoryToSearch == null) {
  135. directoryToSearch = Application.dataPath;
  136. }
  137. var files = Directory.GetFiles(directoryToSearch, fileName, SearchOption.AllDirectories);
  138. if (files.Length == 1) {
  139. return files[0];
  140. }
  141. var errorMessage = String.Format("Unable to locate the file {0}. It's not in the expected location ({1}), and {2} instances of the directory were found in the directory {3}.", fileName, expectedPath, files.Length, directoryToSearch);
  142. throw new Exception(errorMessage);
  143. }
  144. public static void ForceAndroidInternetPermission() {
  145. if (!PlayerSettings.Android.forceInternetPermission) {
  146. PlayerSettings.Android.forceInternetPermission = true;
  147. WebViewLogger.LogWarning("Just a heads-up: 3D WebView changed the Android player setting \"Internet Access\" to \"Require\" to ensure that it can fetch web pages from the internet. (This message will only be logged once.)");
  148. }
  149. }
  150. public static string GetLinkColor() {
  151. return EditorGUIUtility.isProSkin ? "#7faef0ff" : "#11468aff";
  152. }
  153. /// <summary>
  154. /// A polyfill for `Path.Combine(string[])`, which isn't present in .NET 2.0.
  155. /// </summary>
  156. public static string PathCombine(string[] pathComponents) {
  157. if (pathComponents.Length == 0) {
  158. return "";
  159. }
  160. if (pathComponents.Length == 1) {
  161. return pathComponents[0];
  162. }
  163. var path = pathComponents[0];
  164. for (var i = 1; i < pathComponents.Length; i++) {
  165. path = System.IO.Path.Combine(path, pathComponents[i]);
  166. }
  167. return path;
  168. }
  169. public static string TextWithColor(string text, string color) {
  170. return String.Format("<color={0}>{1}</color>", color, text);
  171. }
  172. public static bool XrSdkIsEnabled(string sdkNameFragment) {
  173. // This approach is taken because the legacy Oculus XR plugin identifies itself as "Oculus", but
  174. // the new XR plugin shows up as two devices named "oculus input" and "oculus display". Similarly,
  175. // the MockHMD plugin used to identify itself as "MockHMD" but now it shows up as "MockHMD Head Tracking"
  176. // and "MockHMD Display".
  177. foreach (var sdkName in XrUtils.XRSettings.supportedDevices) {
  178. if (sdkName.ToLower().Contains(sdkNameFragment.ToLower())) {
  179. return true;
  180. }
  181. }
  182. return false;
  183. }
  184. }
  185. }