WebViewPrefab.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. // Copyright (c) 2024 Vuplex Inc. All rights reserved.
  2. //
  3. // Licensed under the Vuplex Commercial Software Library License, you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // https://vuplex.com/commercial-library-license
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. using System;
  15. using UnityEngine;
  16. using UnityEngine.EventSystems;
  17. using UnityEngine.Serialization;
  18. using Vuplex.WebView.Internal;
  19. namespace Vuplex.WebView {
  20. /// <summary>
  21. /// WebViewPrefab is a prefab that makes it easy to view and interact with an IWebView in 3D world space.
  22. /// It takes care of creating an IWebView, displaying its texture, and handling pointer interactions
  23. /// from the user, like clicking, dragging, and scrolling. So, all you need to do is specify a URL or HTML to load,
  24. /// and then the user can view and interact with it. For use in a Canvas, see CanvasWebViewPrefab instead.
  25. /// </summary>
  26. /// <remarks>
  27. /// There are two ways to create a WebViewPrefab:
  28. /// <list type="number">
  29. /// <item>
  30. /// By dragging the WebViewPrefab.prefab file into your scene via the editor and setting its "Initial URL" property.
  31. /// </item>
  32. /// <item>
  33. /// Or by creating an instance programmatically with WebViewPrefab.Instantiate(), waiting for
  34. /// it to initialize, and then calling methods on its WebView property, like LoadUrl().
  35. /// </item>
  36. /// </list>
  37. /// <para>
  38. /// If your use case requires a high degree of customization, you can instead create an IWebView
  39. /// outside of the prefab with Web.CreateWebView().
  40. /// </para>
  41. /// See also:
  42. /// <list type="bullet">
  43. /// <item>CanvasWebViewPrefab: https://developer.vuplex.com/webview/CanvasWebViewPrefab</item>
  44. /// <item>How clicking and scrolling works: https://support.vuplex.com/articles/clicking</item>
  45. /// <item>IWebView: https://developer.vuplex.com/webview/IWebView</item>
  46. /// <item>Web (static methods): https://developer.vuplex.com/webview/Web</item>
  47. /// </list>
  48. /// </remarks>
  49. [HelpURL("https://developer.vuplex.com/webview/WebViewPrefab")]
  50. public partial class WebViewPrefab : BaseWebViewPrefab {
  51. /// <summary>
  52. /// Gets the prefab's collider.
  53. /// </summary>
  54. public Collider Collider { get => _view.GetComponent<Collider>(); }
  55. /// <summary>
  56. /// Determines whether the operating system's native on-screen keyboard is
  57. /// automatically shown when a text input in the webview is focused. The default for
  58. /// WebViewPrefab is `false`.
  59. /// </summary>
  60. /// <seealso cref="IWithNativeOnScreenKeyboard"/>
  61. /// <remarks>
  62. /// The native on-screen keyboard is only supported for the following packages:
  63. /// <list type="bullet">
  64. /// <item>3D WebView for Android (non-Gecko)</item>
  65. /// <item>3D WebView for iOS</item>
  66. /// </list>
  67. /// </remarks>
  68. /// <remarks>
  69. /// 3D WebView for Android with Gecko Engine doesn't support automatically showing the native on-screen keyboard,
  70. /// but you can use Unity's [TouchScreenKeyboard](https://docs.unity3d.com/ScriptReference/TouchScreenKeyboard.html)
  71. /// API to show the keyboard and then send typed characters to the webview like described in [this article](https://support.vuplex.com/articles/how-to-use-a-third-party-keyboard).
  72. /// </remarks>
  73. /// <remarks>
  74. /// On iOS, disabling the keyboard for one webview disables it for all webviews.
  75. /// </remarks>
  76. /// <seealso cref="IWithNativeOnScreenKeyboard"/>
  77. /// <seealso cref="KeyboardEnabled"/>
  78. [Label("Native On-Screen Keyboard (Android and iOS only)")]
  79. [Header("Platform-specific")]
  80. [Tooltip("Determines whether the operating system's native on-screen keyboard is automatically shown when a text input in the webview is focused. The native on-screen keyboard is only supported for the following packages:\n• 3D WebView for Android (non-Gecko)\n• 3D WebView for iOS")]
  81. public bool NativeOnScreenKeyboardEnabled;
  82. /// <summary>
  83. /// Gets or sets the prefab's resolution in pixels per Unity unit.
  84. /// You can change the resolution to make web content appear larger or smaller.
  85. /// The default resolution for WebViewPrefab is `1300`.
  86. /// </summary>
  87. /// <remarks>
  88. /// Setting a lower resolution decreases the pixel density, but has the effect
  89. /// of making web content appear larger. Setting a higher resolution increases
  90. /// the pixel density, but has the effect of making content appear smaller.
  91. /// For more information on scaling web content, see
  92. /// [this support article](https://support.vuplex.com/articles/how-to-scale-web-content).
  93. /// </remarks>
  94. /// <example>
  95. /// <code>
  96. /// // Set the resolution to 1800px per Unity unit.
  97. /// webViewPrefab.Resolution = 1800;
  98. /// </code>
  99. /// </example>
  100. [Label("Resolution (px / Unity unit)")]
  101. [Tooltip("You can change this to make web content appear larger or smaller.")]
  102. [HideInInspector]
  103. [FormerlySerializedAs("InitialResolution")]
  104. public float Resolution = 1300;
  105. /// <summary>
  106. /// Determines the scroll sensitivity. The default sensitivity for WebViewPrefab is `0.005`.
  107. /// </summary>
  108. [HideInInspector]
  109. public float ScrollingSensitivity = 0.005f;
  110. public override Vector2 BrowserToScreenPoint(int xInPixels, int yInPixels) {
  111. if (WebView == null || Camera.main == null) {
  112. return Vector2.zero;
  113. }
  114. var normalizedPoint = WebView.PointToNormalized(xInPixels, yInPixels);
  115. // Clamp x and y to the range [0, WebView.Size].
  116. var clampedNormalizedX = Math.Min(Math.Max(normalizedPoint.x, 0), 1);
  117. var clampedNormalizedY = Math.Min(Math.Max(normalizedPoint.y, 0), 1);
  118. var worldPoint = _viewResizer.TransformPoint(new Vector3(1 - clampedNormalizedX, -1 * clampedNormalizedY, 0));
  119. var screenPoint = Camera.main.WorldToScreenPoint(worldPoint);
  120. return new Vector2(screenPoint.x, Screen.height - screenPoint.y);
  121. }
  122. /// <summary>
  123. /// Creates a new instance with the given dimensions in Unity units.
  124. /// </summary>
  125. /// <remarks>
  126. /// The WebView property is available after initialization completes,
  127. /// which is indicated by WaitUntilInitialized() method.
  128. /// A WebViewPrefab's default resolution is 1300px per Unity unit but can be
  129. /// changed by setting Resolution property.
  130. /// </remarks>
  131. /// <example>
  132. /// <code>
  133. /// // Create a 0.5 x 0.5 instance
  134. /// var webViewPrefab = WebViewPrefab.Instantiate(0.5f, 0.5f);
  135. /// // Position the prefab how we want it
  136. /// webViewPrefab.transform.parent = transform;
  137. /// webViewPrefab.transform.localPosition = new Vector3(0, 0f, 0.5f);
  138. /// webViewPrefab.transform.LookAt(transform);
  139. /// // Load a URL once the prefab finishes initializing
  140. /// await webViewPrefab.WaitUntilInitialized();
  141. /// webViewPrefab.WebView.LoadUrl("https://vuplex.com");
  142. /// </code>
  143. /// </example>
  144. public static WebViewPrefab Instantiate(float width, float height) {
  145. return Instantiate(width, height, new WebViewOptions());
  146. }
  147. /// <summary>
  148. /// Like Instantiate(float, float), except it also accepts an object
  149. /// of options flags that can be used to alter the generated webview's behavior.
  150. /// </summary>
  151. public static WebViewPrefab Instantiate(float width, float height, WebViewOptions options) {
  152. var prefabPrototype = (GameObject)Resources.Load("WebViewPrefab");
  153. var gameObject = (GameObject)Instantiate(prefabPrototype);
  154. var webViewPrefab = gameObject.GetComponent<WebViewPrefab>();
  155. webViewPrefab._sizeForInitialization = new Vector2(width, height);
  156. webViewPrefab._options = options;
  157. return webViewPrefab;
  158. }
  159. /// <summary>
  160. /// Like Instantiate(float, float), except it initializes the instance with an existing, initialized
  161. /// IWebView instance. This causes the WebViewPrefab to use the existing
  162. /// IWebView instance instead of creating a new one. This can be used, for example, to create multiple
  163. /// WebViewPrefabs that are connected to the same IWebView, or to create a prefab for an IWebView
  164. /// created by IWithPopups.PopupRequested.
  165. /// </summary>
  166. /// <example>
  167. /// <code>
  168. /// await firstWebViewPrefab.WaitUntilInitialized();
  169. /// var secondWebViewPrefab = WebViewPrefab.Instantiate(firstWebViewPrefab.WebView);
  170. /// // TODO: Position secondWebViewPrefab to the location where you want to display it.
  171. /// </code>
  172. /// </example>
  173. public static WebViewPrefab Instantiate(IWebView webView) {
  174. var prefabPrototype = (GameObject)Resources.Load("WebViewPrefab");
  175. var gameObject = (GameObject)Instantiate(prefabPrototype);
  176. var webViewPrefab = gameObject.GetComponent<WebViewPrefab>();
  177. webViewPrefab.SetWebViewForInitialization(webView);
  178. return webViewPrefab;
  179. }
  180. /// <summary>
  181. /// Resizes the prefab mesh and webview to the given dimensions in Unity units.
  182. /// </summary>
  183. /// <example>
  184. /// <c>webViewPrefab.Resize(1.2f, 0.5f);</c>
  185. /// </example>
  186. /// <seealso cref="Resolution"/>
  187. public void Resize(float width, float height) {
  188. if (width <= 0f || height <= 0f) {
  189. throw new ArgumentException($"Invalid dimensions: ({width.ToString("n4")}, {height.ToString("n4")})");
  190. }
  191. _sizeInUnityUnits = new Vector2(width, height);
  192. _resizeWebViewIfNeeded();
  193. _setViewSize(width, height);
  194. }
  195. /// <summary>
  196. /// Converts the given world point to a normalized point in the webview.
  197. /// </summary>
  198. public Vector2 WorldToNormalized(Vector3 worldPoint) {
  199. var localPoint = _viewResizer.transform.InverseTransformPoint(worldPoint);
  200. return new Vector2(1 - localPoint.x, -1 * localPoint.y);
  201. }
  202. #region Non-public members
  203. Vector2 _sizeForInitialization = Vector2.zero;
  204. [SerializeField]
  205. [HideInInspector]
  206. Transform _videoRectPositioner;
  207. [SerializeField]
  208. [HideInInspector]
  209. protected Transform _viewResizer;
  210. // Partial method implemented by various 3D WebView packages
  211. // to provide platform-specific warnings.
  212. partial void OnInit();
  213. protected override float _getResolution() {
  214. if (Resolution > 0f) {
  215. return Resolution;
  216. }
  217. WebViewLogger.LogError("Invalid value set for WebViewPrefab.Resolution: " + Resolution);
  218. return 1300;
  219. }
  220. protected override float _getScrollingSensitivity() => ScrollingSensitivity;
  221. protected override bool _getNativeOnScreenKeyboardEnabled() => NativeOnScreenKeyboardEnabled;
  222. protected override ViewportMaterialView _getVideoLayer() {
  223. if (_videoRectPositioner == null) {
  224. return null;
  225. }
  226. return _videoRectPositioner.GetComponentInChildren<ViewportMaterialView>();
  227. }
  228. protected override ViewportMaterialView _getView() {
  229. return transform.Find("WebViewPrefabResizer/WebViewPrefabView").GetComponent<ViewportMaterialView>();
  230. }
  231. async void _initWebViewPrefab() {
  232. try {
  233. OnInit();
  234. #if VUPLEX_XR_INTERACTION_TOOLKIT
  235. WebViewLogger.LogWarning("It looks like you're using a WebViewPrefab with XR Interaction Toolkit. Please use a CanvasWebViewPrefab inside a world space Canvas instead. For more information, please see <em>https://support.vuplex.com/articles/xr-interaction-toolkit</em>.");
  236. #endif
  237. if (_sizeForInitialization == Vector2.zero) {
  238. if (_webViewForInitialization != null) {
  239. _sizeForInitialization = (Vector2)_webViewForInitialization.Size / Resolution;
  240. } else {
  241. // The size was set via the editor instead of through arguments to Instantiate().
  242. _sizeForInitialization = transform.localScale;
  243. _resetLocalScale();
  244. }
  245. }
  246. _viewResizer = transform.GetChild(0);
  247. _videoRectPositioner = _viewResizer.Find("VideoRectPositioner");
  248. _setViewSize(_sizeForInitialization.x, _sizeForInitialization.y);
  249. await _initBase(new Rect(Vector2.zero, _sizeForInitialization));
  250. } catch (Exception exception) {
  251. // Catch any exceptions that occur during initialization because
  252. // some applications terminate the application on uncaught exceptions.
  253. Debug.LogException(exception);
  254. }
  255. }
  256. /// <summary>
  257. /// The top-level WebViewPrefab object's scale must be (1, 1),
  258. /// so the scale that was set via the editor is transferred from WebViewPrefab
  259. /// to WebViewPrefabResizer, and WebViewPrefab is moved to compensate
  260. /// for how WebViewPrefabResizer is moved in _setViewSize.
  261. /// </summary>
  262. void _resetLocalScale() {
  263. var localScale = transform.localScale;
  264. var localPosition = transform.localPosition;
  265. transform.localScale = new Vector3(1, 1, localScale.z);
  266. var offsetMagnitude = 0.5f * localScale.x;
  267. transform.localPosition = transform.localPosition + Quaternion.Euler(transform.localEulerAngles) * new Vector3(offsetMagnitude, 0, 0);
  268. }
  269. protected override void _setVideoLayerPosition(Rect videoRect) {
  270. // The origins of the prefab and the video rect are in their top-right
  271. // corners instead of their top-left corners.
  272. _videoRectPositioner.localPosition = new Vector3(
  273. 1 - (videoRect.x + videoRect.width),
  274. -1 * videoRect.y,
  275. _videoRectPositioner.localPosition.z
  276. );
  277. _videoRectPositioner.localScale = new Vector3(videoRect.width, videoRect.height, _videoRectPositioner.localScale.z);
  278. }
  279. void _setViewSize(float width, float height) {
  280. var depth = _viewResizer.localScale.z;
  281. _viewResizer.localScale = new Vector3(width, height, depth);
  282. var localPosition = _viewResizer.localPosition;
  283. // Set the view resizer so that its middle aligns with the middle of this parent class's gameobject.
  284. localPosition.x = width * -0.5f;
  285. _viewResizer.localPosition = localPosition;
  286. }
  287. void Start() => _initWebViewPrefab();
  288. #endregion
  289. #region Obsolete APIs
  290. // Renamed in v4.2.
  291. [Obsolete("WebViewPrefab.ConvertToScreenPoint() has been renamed to WebViewPrefab.WorldToNormalized(). Please switch to WorldToNormalized().")]
  292. public Vector2 ConvertToScreenPoint(Vector3 worldPoint) => WorldToNormalized(worldPoint);
  293. // Added in v1.0, removed in v3.12.
  294. [Obsolete("WebViewPrefab.Init() has been removed. The WebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called.", true)]
  295. public void Init() {}
  296. // Added in v1.0, removed in v3.12.
  297. [Obsolete("WebViewPrefab.Init() has been removed. The WebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called.", true)]
  298. public void Init(float width, float height) {}
  299. // Added in v1.0, removed in v3.12.
  300. [Obsolete("WebViewPrefab.Init() has been removed. The WebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called.", true)]
  301. public void Init(float width, float height, WebViewOptions options) {}
  302. // Added in v3.8, removed in v3.12.
  303. [Obsolete("WebViewPrefab.Init() has been removed. The WebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called. Please use WebViewPrefab.SetWebViewForInitialization(IWebView) instead.", true)]
  304. public void Init(IWebView webView) {}
  305. // Deprecated in v4.0.
  306. [Obsolete("WebViewPrefab.InitialResolution is now deprecated. Please use WebViewPrefab.Resolution instead.")]
  307. public float InitialResolution {
  308. get => Resolution;
  309. set => Resolution = value;
  310. }
  311. // Added in v2.3.3, removed in v3.5.
  312. [Obsolete("The static WebViewPrefab.ScrollSensitivity property has been removed. Please use the ScrollingSensitivity instance property instead: https://developer.vuplex.com/webview/WebViewPrefab#ScrollingSensitivity", true)]
  313. public static float ScrollSensitivity;
  314. #endregion
  315. }
  316. }