// Copyright (c) 2022 Vuplex Inc. All rights reserved.
//
// Licensed under the Vuplex Commercial Software Library License, you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// https://vuplex.com/commercial-library-license
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.EventSystems;
using Vuplex.WebView.Internal;
namespace Vuplex.WebView {
public abstract class BaseWebViewPrefab : MonoBehaviour {
///
/// Indicates that the prefab was clicked. Note that the prefab automatically
/// calls IWebView.Click() for you.
///
///
/// This event is not supported when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
///
///
///
/// webViewPrefab.Clicked += (sender, eventArgs) => {
/// Debug.Log("WebViewPrefab was clicked at point: " + eventArgs.Point);
/// };
///
///
public virtual event EventHandler Clicked;
///
/// Indicates that the prefab finished initializing,
/// so its WebView property is ready to use.
///
///
public event EventHandler Initialized;
///
/// Indicates that the prefab was scrolled. Note that the prefab automatically
/// calls IWebView.Scroll() for you.
///
///
/// This event is not supported when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
///
///
/// webViewPrefab.Scrolled += (sender, eventArgs) => {
/// Debug.Log($"WebViewPrefab was scrolled. Point: {eventArgs.Point}, scroll delta: {eventArgs.ScrollDelta}");
/// };
///
public virtual event EventHandler Scrolled;
///
/// Determines whether clicking is enabled. The default is `true`.
///
///
/// This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
///
public bool ClickingEnabled = true;
///
/// Determines whether the mouse cursor icon is automatically updated based on interaction with
/// the web page. For example, hovering over a link causes the mouse cursor icon to turn into a pointer hand.
/// The default is `true`. CursorIconsEnabled is currently only supported by 3D WebView for Windows and macOS.
///
///
[Label("Cursor Icons Enabled (Windows and macOS only)")]
[Tooltip("(Windows and macOS only) Sets whether the mouse cursor icon is automatically updated based on interaction with the web page. For example, hovering over a link causes the mouse cursor icon to turn into a pointer hand.")]
public bool CursorIconsEnabled = true;
///
/// Determines how the prefab handles drag interactions.
///
///
/// Important notes:
///
/// - This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
/// -
/// For information on the limitations of drag interactions on iOS and UWP, please see
/// [this article](https://support.vuplex.com/articles/hover-and-drag-limitations).
///
///
///
/// When I drag a scrollbar, why does it scroll the wrong way?
[Tooltip("Determines how the prefab handles drag interactions. Note that This property is ignored when running in Native 2D Mode.")]
public DragMode DragMode = DragMode.DragToScroll;
///
/// Determines the threshold (in web pixels) for triggering a drag. The default is `20`.
///
///
///
/// -
/// When the prefab's DragMode is set to DragToScroll, this property determines
/// the distance that the pointer must drag before it's no longer
/// considered a click.
///
/// -
/// When the prefab's DragMode is set to DragWithinPage, this property determines
/// the distance that the pointer must drag before it triggers
/// a drag within the page.
///
/// - This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
///
///
[Label("Drag Threshold (px)")]
[Tooltip("Determines the threshold (in web pixels) for triggering a drag.")]
public float DragThreshold = 20;
///
/// Determines whether hover interactions are enabled. The default is `true`.
///
///
/// Important notes:
///
/// - This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
/// -
/// For information on the limitations of hovering on iOS and UWP, please see
/// [this article](https://support.vuplex.com/articles/hover-and-drag-limitations).
///
///
///
public bool HoveringEnabled = true;
///
/// If you drag the prefab into the scene via the editor,
/// you can set this property to make it so that the instance
/// automatically loads the given URL after it initializes. To load a new URL
/// at runtime, use IWebView.LoadUrl() instead.
///
/// How to load local files
[Label("Initial URL (optional)")]
[Tooltip("You can set this to the URL that you want to load, or you can leave it blank if you'd rather add a script to load content programmatically with IWebView.LoadUrl() or LoadHtml().")]
[HideInInspector]
public string InitialUrl;
///
/// Determines whether JavaScript console messages from IWebView.ConsoleMessageLogged
/// are printed to the Unity logs. The default is `false`.
///
[Tooltip("Determines whether JavaScript console messages are printed to the Unity logs.")]
public bool LogConsoleMessages = false;
///
/// Gets or sets prefab's material.
///
///
/// This property is unused when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
///
public Material Material {
get { return _view.Material; }
set { _view.Material = value; }
}
///
/// Sets the webview's pixel density, which is its number of physical pixels per logical pixel.
/// The default value is `1`, but increasing it to `2` can make web content appear sharper
/// or less blurry on high DPI displays. PixelDensity is currently only supported by
/// 3D WebView for Windows and macOS.
///
///
///
/// // Increase the pixel density to 2 for high DPI screens.
/// webViewPrefab.PixelDensity = 2;
///
///
///
[Label("Pixel Density (Windows and macOS only)")]
[Tooltip("(Windows and macOS only) Sets the webview's pixel density.")]
public float PixelDensity = 1;
///
/// Determines whether the prefab enables remote debugging by calling Web.EnableRemoteDebugging().
/// The default is `false`.
///
///
[Header("Debugging")]
[Tooltip("Determines whether remote debugging is enabled.")]
public bool RemoteDebuggingEnabled = false;
///
/// Determines whether scrolling is enabled. The default is `true`.
///
///
/// This property is ignored when running in [Native 2D Mode](https://support.vuplex.com/articles/native-2d-mode).
///
public bool ScrollingEnabled = true;
///
/// Gets or sets whether the instance is visible. The default is `true`.
///
public virtual bool Visible {
get { return _view.Visible; }
set {
_view.Visible = value;
if (_videoLayerIsEnabled) {
_videoLayer.Visible = value;
}
}
}
///
/// Returns the prefab's IWebView instance, or `null` if the prefab hasn't finished
/// initializing yet. To detect when the WebView property is no longer null,
/// please use WaitUntilInitialized().
///
///
///
/// await webViewPrefab.WaitUntilInitialized();
/// // Now the WebView property is ready.
/// webViewPrefab.WebView.LoadUrl("https://vuplex.com");
///
///
public IWebView WebView {
get {
if (_cachedWebView == null) {
if (_webViewGameObject == null) {
return null;
}
_cachedWebView = _webViewGameObject.GetComponent();
}
return _cachedWebView;
}
private set {
var monoBehaviour = value as MonoBehaviour;
if (monoBehaviour == null) {
throw new ArgumentException("The IWebView cannot be set successfully because it's not a MonoBehaviour.");
}
_webViewGameObject = monoBehaviour.gameObject;
_cachedWebView = value;
}
}
///
/// Destroys the instance and its children. Note that you don't have
/// to call this method if you destroy the instance's parent with
/// Object.Destroy().
///
///
///
/// // The webview can no longer be used after it's destroyed.
/// webViewPrefab.Destroy();
///
///
public void Destroy() => Destroy(gameObject);
public void SetCutoutRect(Rect rect) => _view.SetCutoutRect(rect);
///
/// Sets options that can be used to alter the webview that the prefab creates
/// during initialization. This method can only be called prior to
/// when the prefab initializes (i.e. directly after instantiating it or setting it to active).
///
public void SetOptionsForInitialization(WebViewOptions options) {
if (WebView != null) {
throw new ArgumentException("SetOptionsForInitialization() was called after the prefab was already initialized. Please call it before initialization instead.");
}
_options = options;
}
///
/// By default, the prefab detects pointer input events like clicks through
/// Unity's event system, but you can use this method to override the way that
/// input events are detected.
///
///
///
/// var yourCustomInputDetector = webViewPrefab.Collider.AddComponent<YourCustomInputDetector>();
/// webViewPrefab.SetPointerInputDetector(yourCustomInputDetector);
///
///
public void SetPointerInputDetector(IPointerInputDetector pointerInputDetector) {
var previousPointerInputDetector = _pointerInputDetector;
_pointerInputDetector = pointerInputDetector;
// If WebView hasn't been set yet, then _initPointerInputDetector
// will get called before it's set to initialize _pointerInputDetector.
if (WebView != null) {
_initPointerInputDetector(WebView, previousPointerInputDetector);
}
}
///
/// By default, the prefab creates a new IWebView during initialization. However,
/// you can call this method before the prefab initializes to pass it an existing,
/// initialized IWebView to use instead. This method can only be called prior to
/// when the prefab initializes (i.e. directly after instantiating it or setting it to active).
///
public void SetWebViewForInitialization(IWebView webView) {
if (WebView != null) {
throw new ArgumentException("SetWebViewForInitialization() was called after the prefab was already initialized. Please call it before initialization instead.");
}
if (webView != null && !webView.IsInitialized) {
throw new ArgumentException("SetWebViewForInitialization(IWebView) was called with an uninitialized webview, but an initialized webview is required.");
}
_webViewForInitialization = webView;
}
///
/// Returns a task that completes when the prefab is initialized,
/// which means that its WebView property is ready for use.
///
///
/// await webViewPrefab.WaitUntilInitialized();
/// // Now the WebView property is ready.
/// webViewPrefab.WebView.LoadUrl("https://vuplex.com");
///
public Task WaitUntilInitialized() {
var taskSource = new TaskCompletionSource();
var isInitialized = WebView != null;
if (isInitialized) {
taskSource.SetResult(true);
} else {
Initialized += (sender, e) => taskSource.SetResult(true);
}
return taskSource.Task;
}
#region Non-public members
float _appliedResolution;
[SerializeField]
[HideInInspector]
ViewportMaterialView _cachedVideoLayer;
[SerializeField]
[HideInInspector]
ViewportMaterialView _cachedView;
IWebView _cachedWebView;
// Used for DragMode.DragToScroll and DragMode.Disabled
bool _clickIsPending;
bool _consoleMessageLoggedHandlerAttached;
bool _dragThresholdReached;
int _heightInPixels { get { return (int)(_sizeInUnityUnits.y * _appliedResolution); }}
bool _loggedDragWarning;
static bool _loggedHoverWarning;
protected WebViewOptions _options;
[SerializeField]
[HideInInspector]
MonoBehaviour _pointerInputDetectorMonoBehaviour;
IPointerInputDetector _pointerInputDetector {
get {
return _pointerInputDetectorMonoBehaviour as IPointerInputDetector;
}
set {
var monoBehaviour = value as MonoBehaviour;
if (monoBehaviour == null) {
throw new ArgumentException("The provided IPointerInputDetector can't be successfully set because it's not a MonoBehaviour");
}
_pointerInputDetectorMonoBehaviour = monoBehaviour;
}
}
bool _pointerIsDown;
Vector2 _pointerDownNormalizedPoint;
Vector2 _previousNormalizedDragPoint;
Vector2 _previousMovePointerPoint;
static bool _remoteDebuggingEnabled;
protected Vector2 _sizeInUnityUnits;
protected ViewportMaterialView _videoLayer {
get {
if (_cachedVideoLayer == null) {
_cachedVideoLayer = _getVideoLayer();
}
return _cachedVideoLayer;
}
}
bool _videoLayerIsEnabled {
get {
return _videoLayer != null && _videoLayer.gameObject.activeSelf;
}
set {
if (_videoLayer != null) {
_videoLayer.gameObject.SetActive(value);
}
}
}
Material _videoMaterial;
protected ViewportMaterialView _view {
get {
if (_cachedView == null) {
_cachedView = _getView();
}
return _cachedView;
}
}
Material _viewMaterial;
protected IWebView _webViewForInitialization;
[SerializeField]
[HideInInspector]
GameObject _webViewGameObject;
int _widthInPixels { get { return (int)(_sizeInUnityUnits.x * _appliedResolution); }}
void _attachWebViewEventHandlers(IWebView webView) {
if (LogConsoleMessages) {
_consoleMessageLoggedHandlerAttached = true;
webView.ConsoleMessageLogged += WebView_ConsoleMessageLogged;
}
// Needed for Vulkan support on Android.
// See the comments in IWithChangingTexture.cs for details.
var webViewWithChangingTexture = webView as IWithChangingTexture;
if (webViewWithChangingTexture != null) {
webViewWithChangingTexture.TextureChanged += WebView_TextureChanged;
}
// Needed for fallback video support on iOS.
var webViewWithFallbackVideo = webView as IWithFallbackVideo;
if (webViewWithFallbackVideo != null && !_options.disableVideo) {
webViewWithFallbackVideo.VideoRectChanged += (sender, eventArgs) => _setVideoRect(eventArgs.Value);
}
}
Vector2Int _convertNormalizedToPixels(Vector2 normalizedPoint) {
return new Vector2Int((int)(normalizedPoint.x * _widthInPixels), (int)(normalizedPoint.y * _heightInPixels));
}
void _disableHoveringIfNeeded(bool preferNative2DMode) {
#if (UNITY_IOS || UNITY_WSA) && !VUPLEX_NO_DISABLING_HOVER_FOR_PERFORMANCE
if (!HoveringEnabled) {
return;
}
if (preferNative2DMode) {
// Hovering isn't detected in Native 2D Mode, so logging a warning is unnecessary.
return;
}
HoveringEnabled = false;
if (!_loggedHoverWarning) {
_loggedHoverWarning = true;
WebViewLogger.LogWarning("WebViewPrefab.HoveringEnabled is automatically set to false by default on iOS and UWP in order to optimize performance. However, you can override this by adding the scripting symbol VUPLEX_NO_DISABLING_HOVER_FOR_PERFORMANCE in player settings. For more info, see https://support.vuplex.com/articles/hover-and-drag-limitations.");
}
#endif
}
void _enableNativeOnScreenKeyboardIfNeeded(IWebView webView) {
if (webView is IWithNativeOnScreenKeyboard) {
var nativeOnScreenKeyboardEnabled = _getNativeOnScreenKeyboardEnabled();
(webView as IWithNativeOnScreenKeyboard).SetNativeOnScreenKeyboardEnabled(nativeOnScreenKeyboardEnabled);
}
}
void _enableRemoteDebuggingIfNeeded() {
// Remote debugging can only be enabled once, before any webviews are initialized.
if (RemoteDebuggingEnabled && !_remoteDebuggingEnabled) {
_remoteDebuggingEnabled = true;
try {
Web.EnableRemoteDebugging();
} catch (Exception ex) {
WebViewLogger.LogError("An exception occurred while enabling remote debugging. On Windows and macOS, this can happen if a prefab with RemoteDebuggingEnabled = true is created after a prior webview has already been initialized. Exception message: " + ex);
}
}
}
protected abstract float _getResolution();
protected abstract bool _getNativeOnScreenKeyboardEnabled();
protected abstract float _getScrollingSensitivity();
protected abstract ViewportMaterialView _getVideoLayer();
protected abstract ViewportMaterialView _getView();
protected async void _initBase(Rect rect, bool preferNative2DMode = false) {
_throwExceptionIfInitialized();
_sizeInUnityUnits = rect.size;
_updateResolutionIfNeeded();
_disableHoveringIfNeeded(preferNative2DMode);
_enableRemoteDebuggingIfNeeded();
// Note: this.WebView is only set after the webview has been initialized to guarantee
// that the property is ready to use as long as it's not null.
var webView = await _initWebView(rect, preferNative2DMode);
_initViews(webView);
_enableNativeOnScreenKeyboardIfNeeded(webView);
_attachWebViewEventHandlers(webView);
// Init the pointer input detector just before setting WebView so that
// SetPointerInputDetector() will behave correctly if it's called before WebView is set.
if (!_native2DModeEnabled(webView)) {
_initPointerInputDetector(webView);
}
// The webview is now fully initialized, so we can now set WebView and raise the Initialized event.
WebView = webView;
Initialized?.Invoke(this, EventArgs.Empty);
// Lastly, load the InitialUrl.
if (!String.IsNullOrWhiteSpace(InitialUrl)) {
if (_webViewForInitialization != null) {
WebViewLogger.LogWarning("Custom InitialUrl value will be ignored because an initialized webview was provided.");
} else {
webView.LoadUrl(InitialUrl.Trim());
}
}
}
void _initViews(IWebView webView) {
if (_native2DModeEnabled(webView)) {
if (_view != null) {
_view.Visible = false;
_view.gameObject.SetActive(false);
}
_videoLayerIsEnabled = false;
return;
}
// Initialize the main view.
_viewMaterial = webView.CreateMaterial();
_view.Material = _viewMaterial;
// Initialize the video view (iOS only).
var webViewWithFallbackVideo = webView as IWithFallbackVideo;
if (webViewWithFallbackVideo != null && webViewWithFallbackVideo.FallbackVideoEnabled) {
_videoMaterial = webViewWithFallbackVideo.CreateVideoMaterial();
_videoLayer.Material = _videoMaterial;
_setVideoRect(Rect.zero);
} else {
_videoLayerIsEnabled = false;
}
}
async Task _initWebView(Rect rect, bool preferNative2DMode) {
if (_webViewForInitialization != null) {
return _webViewForInitialization;
}
var webView = Web.CreateWebView(_options.preferredPlugins);
// Enable Native 2D Mode if needed.
var enableNative2DMode = preferNative2DMode && webView is IWithNative2DMode;
if (enableNative2DMode) {
var native2DWebView = webView as IWithNative2DMode;
await native2DWebView.InitInNative2DMode(rect);
// Hide the webview if Visible has already been set to false.
native2DWebView.SetVisible(_view.Visible);
return webView;
}
_updatePixelDensityIfNeeded(webView);
// (iOS only) Enable fallback video if needed.
var webViewWithFallbackVideo = webView as IWithFallbackVideo;
if (webViewWithFallbackVideo != null && !_options.disableVideo) {
webViewWithFallbackVideo.SetFallbackVideoEnabled(true);
}
await webView.Init(_widthInPixels, _heightInPixels);
// (Windows and macOS only) Enable cursor icons if needed.
var webViewWithCursorType = webView as IWithCursorType;
if (webViewWithCursorType != null && CursorIconsEnabled && !VXUtils.XRSettings.enabled) {
webViewWithCursorType.CursorTypeChanged += (sender, eventArgs) => {
Internal.CursorHelper.SetCursorIcon(eventArgs.Value);
};
}
return webView;
}
void _initPointerInputDetector(IWebView webView, IPointerInputDetector previousPointerInputDetector = null) {
if (previousPointerInputDetector != null) {
previousPointerInputDetector.BeganDrag -= InputDetector_BeganDrag;
previousPointerInputDetector.Dragged -= InputDetector_Dragged;
previousPointerInputDetector.PointerDown -= InputDetector_PointerDown;
previousPointerInputDetector.PointerExited -= InputDetector_PointerExited;
previousPointerInputDetector.PointerMoved -= InputDetector_PointerMoved;
previousPointerInputDetector.PointerUp -= InputDetector_PointerUp;
previousPointerInputDetector.Scrolled -= InputDetector_Scrolled;
}
if (_pointerInputDetector == null) {
_pointerInputDetector = GetComponentInChildren();
}
// Only enable the PointerMoved event if the webview implementation has MovePointer().
_pointerInputDetector.PointerMovedEnabled = (webView as IWithMovablePointer) != null;
_pointerInputDetector.BeganDrag += InputDetector_BeganDrag;
_pointerInputDetector.Dragged += InputDetector_Dragged;
_pointerInputDetector.PointerDown += InputDetector_PointerDown;
_pointerInputDetector.PointerExited += InputDetector_PointerExited;
_pointerInputDetector.PointerMoved += InputDetector_PointerMoved;
_pointerInputDetector.PointerUp += InputDetector_PointerUp;
_pointerInputDetector.Scrolled += InputDetector_Scrolled;
}
void InputDetector_BeganDrag(object sender, EventArgs eventArgs) {
_dragThresholdReached = false;
_previousNormalizedDragPoint = _pointerDownNormalizedPoint;
}
void InputDetector_Dragged(object sender, EventArgs eventArgs) {
if (DragMode == DragMode.Disabled || WebView == null) {
return;
}
var newNormalizedDragPoint = eventArgs.Value;
var previousNormalizedDragPoint = _previousNormalizedDragPoint;
_previousNormalizedDragPoint = newNormalizedDragPoint;
var totalDragDeltaInPixels = _convertNormalizedToPixels(_pointerDownNormalizedPoint - newNormalizedDragPoint);
if (!_dragThresholdReached) {
// _dragThresholdReached needs to be saved, otherwise it could flip from true back
// to false if the user drags back to the original point where the drag started.
_dragThresholdReached = totalDragDeltaInPixels.magnitude > DragThreshold;
}
if (DragMode == DragMode.DragWithinPage) {
if (_dragThresholdReached) {
_movePointerIfNeeded(newNormalizedDragPoint);
}
return;
}
// DragMode is DragToScroll
var normalizedDragDelta = previousNormalizedDragPoint - newNormalizedDragPoint;
_scrollIfNeeded(normalizedDragDelta, _pointerDownNormalizedPoint);
// Check whether to cancel a pending viewport click so that drag-to-scroll
// doesn't unintentionally trigger a click.
if (_clickIsPending) {
if (_dragThresholdReached) {
_clickIsPending = false;
}
}
}
protected virtual void InputDetector_PointerDown(object sender, PointerEventArgs eventArgs) {
_pointerIsDown = true;
_pointerDownNormalizedPoint = eventArgs.Point;
if (!ClickingEnabled || WebView == null) {
return;
}
if (DragMode == DragMode.DragWithinPage) {
var webViewWithPointerDown = WebView as IWithPointerDownAndUp;
if (webViewWithPointerDown != null) {
webViewWithPointerDown.PointerDown(eventArgs.Point, eventArgs.ToPointerOptions());
return;
} else if (!_loggedDragWarning) {
_loggedDragWarning = true;
WebViewLogger.LogWarning($"The WebViewPrefab's DragMode is set to DragWithinPage, but the webview implementation for this platform ({WebView.PluginType}) doesn't support the PointerDown and PointerUp methods needed for dragging within a page. For more info, see https://developer.vuplex.com/webview/IWithPointerDownAndUp.");
// Fallback to setting _clickIsPending so Click() can be called.
}
}
// Defer calling PointerDown() for DragToScroll so that the click can
// be cancelled if drag exceeds the threshold needed to become a scroll.
_clickIsPending = true;
}
void InputDetector_PointerExited(object sender, EventArgs eventArgs) {
if (HoveringEnabled) {
// Remove the hover state when the pointer exits.
_movePointerIfNeeded(Vector2.zero);
}
}
void InputDetector_PointerMoved(object sender, EventArgs eventArgs) {
// InputDetector_Dragged handles calling MovePointer while dragging.
if (_pointerIsDown || !HoveringEnabled) {
return;
}
_movePointerIfNeeded(eventArgs.Value);
}
protected virtual void InputDetector_PointerUp(object sender, PointerEventArgs eventArgs) {
_pointerIsDown = false;
if (!ClickingEnabled || WebView == null) {
return;
}
var webViewWithPointerDownAndUp = WebView as IWithPointerDownAndUp;
if (DragMode == DragMode.DragWithinPage && webViewWithPointerDownAndUp != null) {
var totalDragDeltaInPixels = _convertNormalizedToPixels(_pointerDownNormalizedPoint - eventArgs.Point);
var dragThresholdReached = totalDragDeltaInPixels.magnitude > DragThreshold;
var pointerUpPoint = dragThresholdReached ? eventArgs.Point : _pointerDownNormalizedPoint;
webViewWithPointerDownAndUp.PointerUp(pointerUpPoint, eventArgs.ToPointerOptions());
} else {
if (!_clickIsPending) {
return;
}
_clickIsPending = false;
// PointerDown() and PointerUp() don't support the preventStealingFocus parameter.
if (webViewWithPointerDownAndUp == null || _options.clickWithoutStealingFocus) {
WebView.Click(eventArgs.Point, _options.clickWithoutStealingFocus);
} else {
var pointerOptions = eventArgs.ToPointerOptions();
webViewWithPointerDownAndUp.PointerDown(eventArgs.Point, pointerOptions);
webViewWithPointerDownAndUp.PointerUp(eventArgs.Point, pointerOptions);
}
}
Clicked?.Invoke(this, new ClickedEventArgs(eventArgs.Point));
}
void InputDetector_Scrolled(object sender, ScrolledEventArgs eventArgs) {
var sensitivity = _getScrollingSensitivity();
// The ScrollingSensivity is measured in Unity units because the argument
// passed to Scroll(Vector2) was originally in Unity units (but is now a normalized value).
var scaledScrollDeltaInUnityUnits = eventArgs.ScrollDelta * sensitivity;
var normalizedScrollDelta = new Vector2(scaledScrollDeltaInUnityUnits.x / _sizeInUnityUnits.x, scaledScrollDeltaInUnityUnits.y / _sizeInUnityUnits.y);
_scrollIfNeeded(normalizedScrollDelta, eventArgs.Point);
}
void _movePointerIfNeeded(Vector2 point) {
var webViewWithMovablePointer = WebView as IWithMovablePointer;
if (webViewWithMovablePointer == null) {
return;
}
if (point != _previousMovePointerPoint) {
_previousMovePointerPoint = point;
webViewWithMovablePointer.MovePointer(point);
}
}
bool _native2DModeEnabled(IWebView webView) => webView is IWithNative2DMode && (webView as IWithNative2DMode).Native2DModeEnabled;
protected virtual void OnDestroy() {
if (WebView != null && !WebView.IsDisposed) {
WebView.Dispose();
}
Destroy();
// Unity doesn't automatically destroy materials and textures
// when the GameObject is destroyed.
if (_viewMaterial != null) {
Destroy(_viewMaterial.mainTexture);
Destroy(_viewMaterial);
}
if (_videoMaterial != null) {
Destroy(_videoMaterial.mainTexture);
Destroy(_videoMaterial);
}
}
protected void _resizeWebViewIfNeeded() {
if (WebView != null && WebView.Size != new Vector2(_widthInPixels, _heightInPixels)) {
WebView.Resize(_widthInPixels, _heightInPixels);
}
}
void _scrollIfNeeded(Vector2 scrollDelta, Vector2 point) {
// scrollDelta can be zero when the user drags the cursor off the screen.
if (!ScrollingEnabled || WebView == null || scrollDelta == Vector2.zero) {
return;
}
WebView.Scroll(scrollDelta, point);
Scrolled?.Invoke(this, new ScrolledEventArgs(scrollDelta, point));
}
protected abstract void _setVideoLayerPosition(Rect videoRect);
void _setVideoRect(Rect videoRect) {
if (_videoLayer == null) {
return;
}
_view.SetCutoutRect(videoRect);
_setVideoLayerPosition(videoRect);
// This code applies a cropping rect to the video layer's shader based on what part of the video (if any)
// falls outside of the viewport and therefore needs to be hidden. Note that the dimensions here are divided
// by the videoRect's width or height, because in the videoLayer shader, the width of the videoRect is 1
// and the height is 1 (i.e. the dimensions are normalized).
float videoRectXMin = Math.Max(0, - 1 * videoRect.x / videoRect.width);
float videoRectYMin = Math.Max(0, -1 * videoRect.y / videoRect.height);
float videoRectXMax = Math.Min(1, (1 - videoRect.x) / videoRect.width);
float videoRectYMax = Math.Min(1, (1 - videoRect.y) / videoRect.height);
var videoCropRect = Rect.MinMaxRect(videoRectXMin, videoRectYMin, videoRectXMax, videoRectYMax);
if (videoCropRect == new Rect(0, 0, 1, 1)) {
// The entire video rect fits within the viewport, so set the cropt rect to zero to disable it.
videoCropRect = Rect.zero;
}
_videoLayer.SetCropRect(videoCropRect);
}
void _throwExceptionIfInitialized() {
if (WebView != null) {
throw new InvalidOperationException("Init() cannot be called on a WebViewPrefab that has already been initialized.");
}
}
protected virtual void Update() {
_updateResolutionIfNeeded();
_updatePixelDensityIfNeeded(WebView);
// Check if LogConsoleMessages is changed from false to true at runtime.
if (LogConsoleMessages && !_consoleMessageLoggedHandlerAttached && WebView != null) {
_consoleMessageLoggedHandlerAttached = true;
WebView.ConsoleMessageLogged += WebView_ConsoleMessageLogged;
}
}
void _updatePixelDensityIfNeeded(IWebView webView) {
var webViewWithPixelDensity = webView as IWithPixelDensity;
if (webViewWithPixelDensity == null || PixelDensity == webViewWithPixelDensity.PixelDensity) {
return;
}
try {
webViewWithPixelDensity.SetPixelDensity(PixelDensity);
} catch (ArgumentException ex) {
WebViewLogger.LogError(ex.ToString());
PixelDensity = 1;
}
}
void _updateResolutionIfNeeded() {
var resolution = _getResolution();
if (_appliedResolution != resolution) {
if (resolution > 0.0f) {
_appliedResolution = resolution;
_resizeWebViewIfNeeded();
} else {
WebViewLogger.LogWarning("Ignoring invalid Resolution: " + resolution);
}
}
}
void WebView_ConsoleMessageLogged(object sender, ConsoleMessageEventArgs eventArgs) {
if (!LogConsoleMessages) {
return;
}
var message = "[Web Console] " + eventArgs.Message;
if (eventArgs.Source != null) {
message += $" ({eventArgs.Source}:{eventArgs.Line})";
}
switch (eventArgs.Level) {
case ConsoleMessageLevel.Error:
WebViewLogger.LogError(message, false);
break;
case ConsoleMessageLevel.Warning:
WebViewLogger.LogWarning(message, false);
break;
default:
WebViewLogger.Log(message, false);
break;
}
}
void WebView_TextureChanged(object sender, EventArgs eventArgs) {
var oldTexture = _view.Texture;
if (oldTexture is RenderTexture) {
// The application replaced WebViewPrefab.Material.mainTexture with a RenderTexture
// (for example, to implement MipMaps), so don't change the prefab's texture.
return;
}
_view.Texture = eventArgs.Value;
Destroy(oldTexture);
}
#endregion
// Added in v3.5, removed in v3.7.
[Obsolete("The WebViewPrefab.DragToScrollThreshold property has been removed. Please use DragThreshold instead: https://developer.vuplex.com/webview/WebViewPrefab#DragThreshold", true)]
public float DragToScrollThreshold { get; set; }
}
}