// 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 System.Timers;
using UnityEngine;
namespace Vuplex.WebView.Demos {
///
/// Sets up the AdvancedWebViewDemo scene, which displays web content in a main
/// world-space WebViewPrefab and renders a UI in a second webview to display the current URL
/// and provide back / forward navigation controls.
///
/// Note: The address bar currently only displays the current URL and is not an input.
/// I plan to add a dedicated browser prefab in the future that will include
/// a functional address bar. In the meantime, you can edit the CONTROLS_HTML field
/// below to implement a URL input.
///
///
/// This scene demonstrates the following:
/// - Programmatically instantiating WebViewPrefabs at runtime
/// - Programmatically instantiating an on-screen Keyboard prefab
/// - Using IWebView methods like LoadUrl(), LoadHtml(), GoBack(), and GoForward()
/// - Attaching handlers to the IWebView.UrlChanged and MessageEmitted events
/// - Sending messages from JavaScript to C# and vice versa
/// - Creating a transparent webview using the transparent meta tag
///
/// Links:
/// - WebViewPrefab docs: https://developer.vuplex.com/webview/WebViewPrefab
/// - Sending messages from JavaScript to C# and vice versa: https://support.vuplex.com/articles/how-to-send-messages-from-javascript-to-c-sharp
/// - How to make a transparent webview: https://support.vuplex.com/articles/how-to-make-a-webview-transparent
/// - How clicking works: https://support.vuplex.com/articles/clicking
/// - Other examples: https://developer.vuplex.com/webview/overview#examples
///
class AdvancedWebViewDemo : MonoBehaviour {
Timer _buttonRefreshTimer = new Timer();
WebViewPrefab _controlsWebViewPrefab;
WebViewPrefab _mainWebViewPrefab;
async void Start() {
Debug.Log("[AdvancedWebViewDemo] Just a heads-up: this scene's address bar currently only displays the current URL and is not an input. For more info, please see the comments in AdvancedWebViewDemo.cs.");
// Use a desktop User-Agent to request the desktop versions of websites.
// https://developer.vuplex.com/webview/Web#SetUserAgent
Web.SetUserAgent(false);
// Instantiate a 0.6 x 0.3 webview for the main web content.
// https://developer.vuplex.com/webview/WebViewPrefab#Instantiate
_mainWebViewPrefab = WebViewPrefab.Instantiate(0.6f, 0.3f);
_mainWebViewPrefab.PixelDensity = 2;
_mainWebViewPrefab.transform.parent = transform;
_mainWebViewPrefab.transform.localPosition = new Vector3(0, -0.05f, 0.4f);
_mainWebViewPrefab.transform.localEulerAngles = new Vector3(0, 180, 0);
// Instantiate a second webview above the first to show a UI that
// displays the current URL and provides back / forward navigation buttons.
_controlsWebViewPrefab = WebViewPrefab.Instantiate(0.6f, 0.05f);
_controlsWebViewPrefab.KeyboardEnabled = false;
_controlsWebViewPrefab.transform.parent = _mainWebViewPrefab.transform;
_controlsWebViewPrefab.transform.localPosition = new Vector3(0, 0.06f, 0);
_controlsWebViewPrefab.transform.localEulerAngles = Vector3.zero;
// Add an on-screen keyboard under the main webview.
// https://developer.vuplex.com/webview/Keyboard
var keyboard = Keyboard.Instantiate();
keyboard.transform.SetParent(_mainWebViewPrefab.transform, false);
keyboard.transform.localPosition = new Vector3(0, -0.31f, 0);
keyboard.transform.localEulerAngles = Vector3.zero;
// Set up a timer to allow the state of the back / forward buttons to be
// refreshed one second after a URL change occurs.
_buttonRefreshTimer.AutoReset = false;
_buttonRefreshTimer.Interval = 1000;
_buttonRefreshTimer.Elapsed += ButtonRefreshTimer_Elapsed;
// Wait for the prefabs to initialize because the WebView property of each is null until then.
// https://developer.vuplex.com/webview/WebViewPrefab#WaitUntilInitialized
await Task.WhenAll(new Task[] {
_mainWebViewPrefab.WaitUntilInitialized(),
_controlsWebViewPrefab.WaitUntilInitialized()
});
// Now that the WebViewPrefabs are initialized, we can use the IWebView APIs via its WebView property.
// https://developer.vuplex.com/webview/IWebView
_mainWebViewPrefab.WebView.UrlChanged += MainWebView_UrlChanged;
_mainWebViewPrefab.WebView.LoadUrl("https://www.google.com");
_controlsWebViewPrefab.WebView.MessageEmitted += Controls_MessageEmitted;
_controlsWebViewPrefab.WebView.LoadHtml(CONTROLS_HTML);
// Android Gecko and UWP w/ XR enabled don't support transparent webviews, so set the cutout
// rect to the entire view so that the shader makes its black background pixels transparent.
var pluginType = _controlsWebViewPrefab.WebView.PluginType;
if (pluginType == WebPluginType.AndroidGecko || pluginType == WebPluginType.UniversalWindowsPlatform) {
_controlsWebViewPrefab.SetCutoutRect(new Rect(0, 0, 1, 1));
}
}
void ButtonRefreshTimer_Elapsed(object sender, ElapsedEventArgs eventArgs) {
// Get the main webview's back / forward state and then post a message
// to the controls UI to update its buttons' state.
Vuplex.WebView.Internal.ThreadDispatcher.RunOnMainThread(async () => {
var canGoBack = await _mainWebViewPrefab.WebView.CanGoBack();
var canGoForward = await _mainWebViewPrefab.WebView.CanGoForward();
var serializedMessage = $"{{ \"type\": \"SET_BUTTONS\", \"canGoBack\": {canGoBack.ToString().ToLowerInvariant()}, \"canGoForward\": {canGoForward.ToString().ToLowerInvariant()} }}";
_controlsWebViewPrefab.WebView.PostMessage(serializedMessage);
});
}
void Controls_MessageEmitted(object sender, EventArgs eventArgs) {
if (eventArgs.Value == "CONTROLS_INITIALIZED") {
// The controls UI won't be initialized in time to receive the first UrlChanged event,
// so explicitly set the initial URL after the controls UI indicates it's ready.
_setDisplayedUrl(_mainWebViewPrefab.WebView.Url);
return;
}
var message = eventArgs.Value;
if (message == "GO_BACK") {
_mainWebViewPrefab.WebView.GoBack();
} else if (message == "GO_FORWARD") {
_mainWebViewPrefab.WebView.GoForward();
}
}
void MainWebView_UrlChanged(object sender, UrlChangedEventArgs eventArgs) {
_setDisplayedUrl(eventArgs.Url);
_buttonRefreshTimer.Start();
}
void _setDisplayedUrl(string url) {
if (_controlsWebViewPrefab.WebView != null) {
var serializedMessage = $"{{ \"type\": \"SET_URL\", \"url\": \"{url}\" }}";
_controlsWebViewPrefab.WebView.PostMessage(serializedMessage);
}
}
const string CONTROLS_HTML = @"
";
}
}