CanvasWebViewPrefab.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. using System;
  17. using UnityEngine;
  18. using Vuplex.WebView.Internal;
  19. namespace Vuplex.WebView {
  20. /// <summary>
  21. /// CanvasWebViewPrefab is a prefab that makes it easy to view and interact with an IWebView in a 2D Canvas.
  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 outside of a Canvas, see WebViewPrefab instead.
  25. /// </summary>
  26. /// <remarks>
  27. /// There are two ways to create a CanvasWebViewPrefab:
  28. /// <list type="number">
  29. /// <item>
  30. /// By dragging the CanvasWebViewPrefab.prefab file into your scene via the editor and setting its "Initial URL" property.</item>
  31. /// <item>
  32. /// Or by creating an instance programmatically with CanvasWebViewPrefab.Instantiate(), waiting for
  33. /// it to initialize, and then calling methods on its WebView property, like LoadUrl().
  34. /// </item>
  35. /// </list>
  36. /// <para>
  37. /// If your use case requires a high degree of customization, you can instead create an IWebView
  38. /// outside of the prefab with Web.CreateWebView().
  39. /// </para>
  40. /// See also:
  41. /// <list type="bullet">
  42. /// <item>WebViewPrefab: https://developer.vuplex.com/webview/WebViewPrefab</item>
  43. /// <item>How clicking and scrolling works: https://support.vuplex.com/articles/clicking</item>
  44. /// <item>IWebView: https://developer.vuplex.com/webview/IWebView</item>
  45. /// <item>Web (static methods): https://developer.vuplex.com/webview/Web</item>
  46. /// </list>
  47. /// </remarks>
  48. [HelpURL("https://developer.vuplex.com/webview/CanvasWebViewPrefab")]
  49. public partial class CanvasWebViewPrefab : BaseWebViewPrefab {
  50. public override event EventHandler<ClickedEventArgs> Clicked {
  51. add {
  52. if (_native2DModeActive) {
  53. _logNative2DModeWarning("The CanvasWebViewPrefab.Clicked event is not supported in Native 2D Mode.");
  54. }
  55. base.Clicked += value;
  56. }
  57. remove {
  58. base.Clicked -= value;
  59. }
  60. }
  61. public override event EventHandler<ScrolledEventArgs> Scrolled {
  62. add {
  63. if (_native2DModeActive) {
  64. _logNative2DModeWarning("The CanvasWebViewPrefab.Scrolled event is not supported in Native 2D Mode.");
  65. }
  66. base.Scrolled += value;
  67. }
  68. remove {
  69. base.Scrolled -= value;
  70. }
  71. }
  72. /// <summary>
  73. /// Enables or disables Native 2D Mode, which makes it so that 3D WebView positions a native 2D webview in front of the Unity game view
  74. /// instead of displaying web content as a texture in the Unity scene. If the 3D WebView package
  75. /// in use doesn't support Native 2D Mode, then the default rendering mode is used instead.
  76. /// For more info on Native 2D Mode, please see [this article](https://support.vuplex.com/articles/native-2d-mode).
  77. /// </summary>
  78. /// <remarks>
  79. /// Important notes:
  80. /// <list type="bullet">
  81. /// <item>
  82. /// Native 2D Mode is only supported for 3D WebView for Android (non-Gecko) and 3D WebView for iOS.
  83. /// For other packages, the default render mode is used instead.
  84. /// </item>
  85. /// <item>Native 2D Mode requires that the canvas's render mode be set to "Screen Space - Overlay".</item>
  86. /// </list>
  87. /// </remarks>
  88. [Label("Native 2D Mode (Android, iOS, WebGL, and UWP only)")]
  89. [Tooltip("Native 2D Mode positions a native 2D webview in front of the Unity game view instead of rendering web content as a texture in the Unity scene. Native 2D Mode provides better performance on iOS and UWP, because the default mode of rendering web content to a texture is slower. \n\nImportant notes:\n• Native 2D Mode is only supported for Android (non-Gecko), iOS, WebGL, and UWP. For the other 3D WebView packages, the default render mode is used instead.\n• Native 2D Mode requires that the canvas's render mode be set to \"Screen Space - Overlay\".")]
  90. [HideInInspector]
  91. [Header("Platform-specific")]
  92. public bool Native2DModeEnabled;
  93. /// <summary>
  94. /// Determines whether the operating system's native on-screen keyboard is
  95. /// automatically shown when a text input in the webview is focused. The default for
  96. /// CanvasWebViewPrefab is `true`.
  97. /// </summary>
  98. /// <remarks>
  99. /// The native on-screen keyboard is only supported for the following packages:
  100. /// <list type="bullet">
  101. /// <item>3D WebView for Android (non-Gecko)</item>
  102. /// <item>3D WebView for iOS</item>
  103. /// </list>
  104. /// </remarks>
  105. [Label("Native On-Screen Keyboard (Android and iOS only)")]
  106. [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")]
  107. public bool NativeOnScreenKeyboardEnabled = true;
  108. /// <summary>
  109. /// Sets the webview's initial resolution in pixels per Unity unit.
  110. /// You can change the resolution to make web content appear larger or smaller.
  111. /// For more information on scaling web content, see
  112. /// [this support article](https://support.vuplex.com/articles/how-to-scale-web-content).
  113. /// </summary>
  114. /// <remarks>
  115. /// This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
  116. /// </remarks>
  117. [Label("Initial Resolution (px / Unity unit)")]
  118. [Tooltip("You can change this to make web content appear larger or smaller. Note that This property is ignored when running in Native 2D Mode.")]
  119. [HideInInspector]
  120. public float InitialResolution = 1;
  121. /// <summary>
  122. /// Determines the scroll sensitivity. The default sensitivity for CanvasWebViewPrefab is `15`.
  123. /// </summary>
  124. /// <remarks>
  125. /// This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
  126. /// </remarks>
  127. [HideInInspector]
  128. [Tooltip("Determines the scroll sensitivity. Note that This property is ignored when running in Native 2D Mode.")]
  129. public float ScrollingSensitivity = 15;
  130. public override bool Visible {
  131. get {
  132. var native2DWebView = _getNative2DWebViewIfActive();
  133. if (native2DWebView != null) {
  134. return native2DWebView.Visible;
  135. }
  136. return base.Visible;
  137. }
  138. set {
  139. var native2DWebView = _getNative2DWebViewIfActive();
  140. if (native2DWebView != null) {
  141. native2DWebView.SetVisible(value);
  142. return;
  143. }
  144. base.Visible = value;
  145. }
  146. }
  147. /// <summary>
  148. /// Creates a new instance.
  149. /// </summary>
  150. /// <remarks>
  151. /// The WebView property is available after initialization completes,
  152. /// which is indicated by WaitUntilInitialized().
  153. /// </remarks>
  154. /// <example>
  155. /// <code>
  156. /// // Create a CanvasWebViewPrefab
  157. /// var canvasWebViewPrefab = CanvasWebViewPrefab.Instantiate();
  158. /// // Position the prefab how we want it
  159. /// var canvas = GameObject.Find("Canvas");
  160. /// canvasWebViewPrefab.transform.parent = canvas.transform;
  161. /// var rectTransform = canvasWebViewPrefab.transform as RectTransform;
  162. /// rectTransform.anchoredPosition3D = Vector3.zero;
  163. /// rectTransform.offsetMin = Vector2.zero;
  164. /// rectTransform.offsetMax = Vector2.zero;
  165. /// canvasWebViewPrefab.transform.localScale = Vector3.one;
  166. /// // Load a URL once the prefab finishes initializing
  167. /// await canvasWebViewPrefab.WaitUntilInitialized();
  168. /// canvasWebViewPrefab.WebView.LoadUrl("https://vuplex.com");
  169. /// </code>
  170. /// </example>
  171. public static CanvasWebViewPrefab Instantiate() {
  172. return Instantiate(new WebViewOptions());
  173. }
  174. /// <summary>
  175. /// Like Instantiate(), except it also accepts an object
  176. /// of options flags that can be used to alter the generated webview's behavior.
  177. /// </summary>
  178. public static CanvasWebViewPrefab Instantiate(WebViewOptions options) {
  179. var prefabPrototype = (GameObject) Resources.Load("CanvasWebViewPrefab");
  180. var gameObject = (GameObject) Instantiate(prefabPrototype);
  181. var canvasWebViewPrefab = gameObject.GetComponent<CanvasWebViewPrefab>();
  182. canvasWebViewPrefab._options = options;
  183. return canvasWebViewPrefab;
  184. }
  185. /// <summary>
  186. /// Like `Instantiate()`, except it initializes the instance with an existing, initialized
  187. /// `IWebView` instance. This causes the `CanvasWebViewPrefab` to use the existing
  188. /// `IWebView` instance instead of creating a new one.
  189. /// </summary>
  190. public static CanvasWebViewPrefab Instantiate(IWebView webView) {
  191. var prefabPrototype = (GameObject) Resources.Load("CanvasWebViewPrefab");
  192. var gameObject = (GameObject) Instantiate(prefabPrototype);
  193. var canvasWebViewPrefab = gameObject.GetComponent<CanvasWebViewPrefab>();
  194. canvasWebViewPrefab.SetWebViewForInitialization(webView);
  195. return canvasWebViewPrefab;
  196. }
  197. [Obsolete("CanvasWebViewPrefab.Init() has been removed. The CanvasWebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called.", true)]
  198. public void Init() {}
  199. [Obsolete("CanvasWebViewPrefab.Init() has been removed. The CanvasWebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called.", true)]
  200. public void Init(WebViewOptions options) {}
  201. [Obsolete("CanvasWebViewPrefab.Init() has been removed. The CanvasWebViewPrefab script now initializes itself automatically, so Init() no longer needs to be called. Please use CanvasWebViewPrefab.SetWebViewForInitialization(IWebView) instead.", true)]
  202. public void Init(IWebView webView) {}
  203. RectTransform _cachedRectTransform;
  204. Canvas _canvas {
  205. get {
  206. if (_canvasGetter == null) {
  207. _canvasGetter = new CachingGetter<Canvas>(GetComponentInParent<Canvas>, 1, this);
  208. }
  209. return _canvasGetter.GetValue();
  210. }
  211. }
  212. CachingGetter<Canvas> _canvasGetter;
  213. bool _native2DModeActive {
  214. get {
  215. var webViewWith2DMode = WebView as IWithNative2DMode;
  216. return webViewWith2DMode != null && webViewWith2DMode.Native2DModeEnabled;
  217. }
  218. }
  219. RectTransform _rectTransform {
  220. get {
  221. if (_cachedRectTransform == null) {
  222. _cachedRectTransform = GetComponent<RectTransform>();
  223. }
  224. return _cachedRectTransform;
  225. }
  226. }
  227. bool _setCustomPointerInputDetector;
  228. // Partial method implemented by various 3D WebView packages
  229. // to provide platform-specific warnings.
  230. partial void OnInit();
  231. bool _canNative2DModeBeEnabled(bool logWarnings = false) {
  232. if (_canvas?.renderMode == RenderMode.WorldSpace) {
  233. if (logWarnings) {
  234. _logNative2DModeWarning("CanvasWebViewPrefab.Native2DModeEnabled is enabled but the canvas's render mode is set to World Space, so Native 2D Mode will not be enabled. In order to use Native 2D Mode, please switch the canvas's render mode to \"Screen Space - Overlay\" or \"Screen Space - Camera\".");
  235. }
  236. return false;
  237. }
  238. if (XRUtils.XRSettings.enabled) {
  239. if (logWarnings) {
  240. _logNative2DModeWarning("CanvasWebViewPrefab.Native2DModeEnabled is enabled but XR is enabled, so Native 2D Mode will not be enabled.");
  241. }
  242. return false;
  243. }
  244. return true;
  245. }
  246. protected override Vector2 _convertRatioPointToUnityUnits(Vector2 point) {
  247. // Use Vector2.Scale() because Vector2 * Vector2 isn't supported in Unity 2017.
  248. return Vector2.Scale(point, _rectTransform.rect.size);
  249. }
  250. protected override float _getInitialResolution() {
  251. return InitialResolution;
  252. }
  253. IWithNative2DMode _getNative2DWebViewIfActive() {
  254. var webViewWith2DMode = WebView as IWithNative2DMode;
  255. if (webViewWith2DMode != null && webViewWith2DMode.Native2DModeEnabled) {
  256. return webViewWith2DMode;
  257. }
  258. return null;
  259. }
  260. protected override bool _getNativeOnScreenKeyboardEnabled() {
  261. return NativeOnScreenKeyboardEnabled;
  262. }
  263. protected override float _getScrollingSensitivity() {
  264. return ScrollingSensitivity;
  265. }
  266. Rect _getScreenSpaceRect() {
  267. var canvas = _canvas;
  268. if (canvas == null) {
  269. WebViewLogger.LogError("Unable to determine the screen space rect for Native 2D Mode because the CanvasWebViewPrefab is not placed in a Canvas. Please place the CanvasWebViewPrefab as the child of a Unity UI Canvas.");
  270. return Rect.zero;
  271. }
  272. var worldCorners = new Vector3[4];
  273. _rectTransform.GetWorldCorners(worldCorners);
  274. var topLeftCorner = worldCorners[1];
  275. var bottomRightCorner = worldCorners[3];
  276. if (canvas.renderMode != RenderMode.ScreenSpaceOverlay) {
  277. var camera = canvas.worldCamera;
  278. if (camera == null) {
  279. WebViewLogger.LogError("Unable to determine the screen space rect for Native 2D Mode because the Canvas's render camera is not set. Please set the Canvas's \"Render Camera\" setting or change its render mode to \"Screen Space - Overlay\".");
  280. } else {
  281. topLeftCorner = camera.WorldToScreenPoint(topLeftCorner);
  282. bottomRightCorner = camera.WorldToScreenPoint(bottomRightCorner);
  283. }
  284. }
  285. var x = topLeftCorner.x;
  286. var y = Screen.height - topLeftCorner.y;
  287. var width = bottomRightCorner.x - topLeftCorner.x;
  288. var height = topLeftCorner.y - bottomRightCorner.y;
  289. return new Rect(x, y, width, height);
  290. }
  291. Rect? _getScreenSpaceRectIfNative2DModeIsEnabled() {
  292. if (!Native2DModeEnabled) {
  293. return null;
  294. }
  295. if (_canNative2DModeBeEnabled(true)) {
  296. return _getScreenSpaceRect();
  297. }
  298. return null;
  299. }
  300. protected override ViewportMaterialView _getVideoLayer() {
  301. return transform.Find("VideoLayer").GetComponent<ViewportMaterialView>();
  302. }
  303. protected override ViewportMaterialView _getView() {
  304. return transform.Find("CanvasWebViewPrefabView").GetComponent<ViewportMaterialView>();
  305. }
  306. void _initCanvasPrefab() {
  307. OnInit();
  308. Initialized += _logNative2DRecommendationIfNeeded;
  309. _init(_rectTransform.rect.size, _getScreenSpaceRectIfNative2DModeIsEnabled());
  310. }
  311. void _logNative2DModeWarning(string message) {
  312. WebViewLogger.LogWarning(message + " For more info, please see this article: <em>https://support.vuplex.com/articles/native-2d-mode</em>");
  313. }
  314. void _logNative2DRecommendationIfNeeded(object sender, EventArgs eventArgs) {
  315. var webViewWith2DMode = WebView as IWithNative2DMode;
  316. if (_canNative2DModeBeEnabled() && webViewWith2DMode != null && !webViewWith2DMode.Native2DModeEnabled) {
  317. WebViewLogger.LogTip("This platform supports Native 2D Mode, so consider enabling CanvasWebViewPrefab.Native2DModeEnabled for best results. For more info, see https://support.vuplex.com/articles/native-2d-mode .");
  318. }
  319. }
  320. void OnDisable() {
  321. // 强制横屏并禁止自动旋转
  322. // When in Native 2D Mode, hide the webview when the CanvasWebViewPrefab is deactivated.
  323. var native2DWebView = _getNative2DWebViewIfActive();
  324. if (native2DWebView != null) {
  325. native2DWebView.SetVisible(false);
  326. }
  327. }
  328. void OnEnable() {
  329. // 强制横屏并禁止自动旋转
  330. // When in Native 2D Mode, show the webview when the CanvasWebViewPrefab is activated.
  331. var native2DWebView = _getNative2DWebViewIfActive();
  332. if (native2DWebView != null) {
  333. native2DWebView.SetVisible(true);
  334. }
  335. }
  336. protected override void _setVideoLayerPosition(Rect videoRect) {
  337. var videoRectTransform = _videoLayer.transform as RectTransform;
  338. // Use Vector2.Scale() because Vector2 * Vector2 isn't supported in Unity 2017.
  339. videoRectTransform.anchoredPosition = Vector2.Scale(Vector2.Scale(videoRect.position, _rectTransform.rect.size), new Vector2(1, -1));
  340. videoRectTransform.sizeDelta = Vector2.Scale(videoRect.size, _rectTransform.rect.size);
  341. }
  342. void Start() {
  343. _initCanvasPrefab();
  344. }
  345. void Update() {
  346. if (WebView == null) {
  347. return;
  348. }
  349. // Handle updating the rect for a native 2D webview.
  350. var native2DWebView = _getNative2DWebViewIfActive();
  351. if (native2DWebView != null) {
  352. var screenSpaceRect = _getScreenSpaceRect();
  353. if (native2DWebView.Rect != screenSpaceRect) {
  354. native2DWebView.SetRect(screenSpaceRect);
  355. }
  356. return;
  357. }
  358. // Handle resizing a regular webview.
  359. var rect = _rectTransform.rect;
  360. if (WebView.Size != rect.size) {
  361. WebView.Resize(rect.width, rect.height);
  362. }
  363. }
  364. }
  365. }