121
This commit is contained in:
parent
bf0efcd1a2
commit
fc6b454cb5
|
@ -19,3 +19,5 @@
|
||||||
/SXElectricalInspection/obj
|
/SXElectricalInspection/obj
|
||||||
/SXElectricalInspection/Temp
|
/SXElectricalInspection/Temp
|
||||||
/SXElectricalInspection/UserSettings
|
/SXElectricalInspection/UserSettings
|
||||||
|
/SXElectricalInspection/Assets/Vuplex
|
||||||
|
/SXElectricalInspection/Assets/ZFBrowser
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 56b8faaae4b14ef46be6a4552ecae7fb
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1447088713
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,10 +0,0 @@
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
public class FlagsFieldAttribute : PropertyAttribute {}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 578312a754d9da142ad199b54e09488f
|
|
||||||
timeCreated: 1450462477
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 35bf0bd4ded61a34f9be34f6738e4010
|
|
||||||
timeCreated: 1447801917
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 10
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,216 +0,0 @@
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using AOT;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callbacks used by Browser.cs
|
|
||||||
/// Man, that file's getting big. (Breaking it up would likely involve backwards-incompatible API changes, so
|
|
||||||
/// let it be for now.)
|
|
||||||
/// </summary>
|
|
||||||
public partial class Browser {
|
|
||||||
/// <summary>
|
|
||||||
/// Map of browserId => Browser fro looking up C# objects when we get a native callback.
|
|
||||||
///
|
|
||||||
/// Note that this reflects the native state, that is, we include Browsers that havne't fully initialized yet,
|
|
||||||
/// but instead list what we need to lookup callbacks coming from the native side.
|
|
||||||
///
|
|
||||||
/// (We used to rely on closures and generated trampolines for this data, but il2cpp doens't support that.)
|
|
||||||
/// </summary>
|
|
||||||
internal static Dictionary<int, Browser> allBrowsers = new Dictionary<int, Browser>();
|
|
||||||
|
|
||||||
private static Browser GetBrowser(int browserId) {
|
|
||||||
lock (allBrowsers) {
|
|
||||||
Browser ret;
|
|
||||||
if (allBrowsers.TryGetValue(browserId, out ret)) return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.LogWarning("Got a callback for brower id " + browserId + " which doesn't exist.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.ForwardJSCallFunc))]
|
|
||||||
private static void CB_ForwardJSCallFunc(int browserId, int callbackId, string data, int size) {
|
|
||||||
var browser = GetBrowser(browserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
|
|
||||||
JSResultFunc cb;
|
|
||||||
if (!browser.registeredCallbacks.TryGetValue(callbackId, out cb)) {
|
|
||||||
Debug.LogWarning("Got a JS callback for event " + callbackId + ", but no such event is registered.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isError = false;
|
|
||||||
if (data.StartsWith("fail-")) {
|
|
||||||
isError = true;
|
|
||||||
data = data.Substring(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONNode node;
|
|
||||||
try {
|
|
||||||
node = JSONNode.Parse(data);
|
|
||||||
} catch (SerializationException) {
|
|
||||||
Debug.LogWarning("Invalid JSON sent from browser: " + data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
cb(node, isError);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
//user's function died, log it and carry on
|
|
||||||
Debug.LogException(ex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.ChangeFunc))]
|
|
||||||
private static void CB_ChangeFunc(int browserId, BrowserNative.ChangeType changeType, string arg1) {
|
|
||||||
//Note: we may have been Object.Destroy'd at this point, so guard against that.
|
|
||||||
//That means we can't trust that `browser == null` means we don't have a browser, we may have an object that
|
|
||||||
//is destroyed, but not actually null.
|
|
||||||
Browser browser;
|
|
||||||
lock (allBrowsers) {
|
|
||||||
if (!allBrowsers.TryGetValue(browserId, out browser)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeType == BrowserNative.ChangeType.CHT_BROWSER_CLOSE) {
|
|
||||||
//We can't continue if the browser is closed, so goodbye.
|
|
||||||
|
|
||||||
//At this point, we may or may not be destroyed, but if not, become destroyed.
|
|
||||||
//Debug.Log("Got close notification for " + unsafeBrowserId);
|
|
||||||
if (browser) {
|
|
||||||
//Need to be destroyed.
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
Destroy(browser.gameObject);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//If we are (Unity) destroyed, we won't get another update, so we can't rely on thingsToDo
|
|
||||||
//That said, there's not anything else for us to do but step out of allThingsToRemember.
|
|
||||||
}
|
|
||||||
|
|
||||||
//The native side has acknowledged it's done, now we can finally let the native trampolines be GC'd
|
|
||||||
lock (allThingsToRemember) allThingsToRemember.Remove(browser.unsafeBrowserId);
|
|
||||||
|
|
||||||
//Now that we know we can allow ourselves to be GC'd and destroyed (without leaking to allThingsToRemember)
|
|
||||||
//go ahead and allow it. (And we won't get any more native callbacks at this point.)
|
|
||||||
lock (allBrowsers) allBrowsers.Remove(browserId);
|
|
||||||
|
|
||||||
//Just in case someone tries to call something, make sure CheckSanity and such fail.
|
|
||||||
browser.browserId = 0;
|
|
||||||
} else if (browser) {
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => browser.OnItemChange(changeType, arg1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.DisplayDialogFunc))]
|
|
||||||
private static void CB_DisplayDialogFunc(
|
|
||||||
int browserId, BrowserNative.DialogType dialogType, IntPtr textPtr,
|
|
||||||
IntPtr promptTextPtr, IntPtr sourceURL
|
|
||||||
) {
|
|
||||||
var browser = GetBrowser(browserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
var text = Util.PtrToStringUTF8(textPtr);
|
|
||||||
var promptText = Util.PtrToStringUTF8(promptTextPtr);
|
|
||||||
//var url = Util.PtrToStringUTF8(urlPtr);
|
|
||||||
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
browser.CreateDialogHandler();
|
|
||||||
browser.dialogHandler.HandleDialog(dialogType, text, promptText);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.ShowContextMenuFunc))]
|
|
||||||
private static void CB_ShowContextMenuFunc(
|
|
||||||
int browserId, string json, int x, int y, BrowserNative.ContextMenuOrigin origin
|
|
||||||
) {
|
|
||||||
var browser = GetBrowser(browserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
if (json != null && (browser.allowContextMenuOn & origin) == 0) {
|
|
||||||
//ignore this
|
|
||||||
BrowserNative.zfb_sendContextMenuResults(browserId, -1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
if (json != null) browser.CreateDialogHandler();
|
|
||||||
if (browser.dialogHandler != null) browser.dialogHandler.HandleContextMenu(json, x, y);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.ConsoleFunc))]
|
|
||||||
private static void CB_ConsoleFunc(int browserId, string message, string source, int line) {
|
|
||||||
var browser = GetBrowser(browserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
browser.onConsoleMessage(message, source + ":" + line);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.ReadyFunc))]
|
|
||||||
private static void CB_ReadyFunc(int browserId) {
|
|
||||||
var browser = GetBrowser(browserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
//We could be on any thread at this time, so schedule the callbacks to fire during the next InputUpdate
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
browser.browserId = browserId;
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
|
||||||
browser.onNativeReady(browserId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.NavStateFunc))]
|
|
||||||
private static void CB_NavStateFunc(int browserId, bool canGoBack, bool canGoForward, bool lodaing, IntPtr urlRaw) {
|
|
||||||
var browser = GetBrowser(browserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
var url = Util.PtrToStringUTF8(urlRaw);
|
|
||||||
|
|
||||||
lock (browser.thingsToDo) browser.thingsToDo.Add(() => {
|
|
||||||
browser.navState.canGoBack = canGoBack;
|
|
||||||
browser.navState.canGoForward = canGoForward;
|
|
||||||
browser.navState.loading = lodaing;
|
|
||||||
browser.navState.url = url;
|
|
||||||
|
|
||||||
browser._url = url;//update the inspector
|
|
||||||
|
|
||||||
browser.onNavStateChange();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.NewWindowFunc))]
|
|
||||||
private static void CB_NewWindowFunc(int creatorBrowserId, int newBrowserId, IntPtr urlPtr) {
|
|
||||||
var browser = GetBrowser(creatorBrowserId);
|
|
||||||
if (!browser) return;
|
|
||||||
|
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
var url = Util.PtrToStringUTF8(urlPtr);
|
|
||||||
if (url == "about:inspector" || browser.newWindowAction == NewWindowAction.NewWindow) lock (browser.thingsToDo) {
|
|
||||||
browser.thingsToDo.Add(() => {
|
|
||||||
PopUpBrowser.Create(newBrowserId);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
lock (browser.thingsToDo) {
|
|
||||||
browser.thingsToDo.Add(() => {
|
|
||||||
var newBrowser = browser.newWindowHandler.CreateBrowser(browser);
|
|
||||||
newBrowser.RequestNativeBrowser(newBrowserId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: a30036008e7d3434291fcfba205bf239
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,170 +0,0 @@
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using CursorType = ZenFulcrum.EmbeddedBrowser.BrowserNative.CursorType;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages finding and copying cursors for you.
|
|
||||||
* Each instance has one active cursor and a Texture2D you can read from to use it.
|
|
||||||
*/
|
|
||||||
public class BrowserCursor {
|
|
||||||
public class CursorInfo {
|
|
||||||
public int atlasOffset;
|
|
||||||
public Vector2 hotspot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<CursorType, CursorInfo> mapping = new Dictionary<CursorType, CursorInfo>();
|
|
||||||
|
|
||||||
private static bool loaded = false;
|
|
||||||
|
|
||||||
private static int size;
|
|
||||||
private static Texture2D allCursors;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when the mouse cursor's appearance or hotspot changes.
|
|
||||||
/// Also fired when the mouse enters/leaves the browser.
|
|
||||||
/// </summary>
|
|
||||||
public event Action cursorChange = () => {};
|
|
||||||
|
|
||||||
private static void Load() {
|
|
||||||
if (loaded) return;
|
|
||||||
allCursors = Resources.Load<Texture2D>("Browser/Cursors");
|
|
||||||
if (!allCursors) throw new Exception("Failed to find browser allCursors");
|
|
||||||
|
|
||||||
size = allCursors.height;
|
|
||||||
|
|
||||||
var listObj = Resources.Load<TextAsset>("Browser/Cursors");
|
|
||||||
|
|
||||||
foreach (var row in listObj.text.Split('\n')) {
|
|
||||||
var info = row.Split(',');
|
|
||||||
|
|
||||||
var k = (CursorType)Enum.Parse(typeof(CursorType), info[0]);
|
|
||||||
var v = new CursorInfo() {
|
|
||||||
atlasOffset = int.Parse(info[1]),
|
|
||||||
hotspot = new Vector2(int.Parse(info[2]), int.Parse(info[3])),
|
|
||||||
};
|
|
||||||
mapping[k] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Texture for the current cursor.
|
|
||||||
* If the cursor should be hidden, this will be null.
|
|
||||||
*/
|
|
||||||
public virtual Texture2D Texture { get; protected set; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hotspot for the current cursor. (0, 0) indicates the top-left of the texture is the hotspot.
|
|
||||||
* (1, 1) indicates the bottom-right.
|
|
||||||
*/
|
|
||||||
public virtual Vector2 Hotspot { get; protected set; }
|
|
||||||
|
|
||||||
private bool _hasMouse;
|
|
||||||
/// <summary>
|
|
||||||
/// True when the mouse is over the browser, false otherwise.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasMouse {
|
|
||||||
get {
|
|
||||||
return _hasMouse;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
_hasMouse = value;
|
|
||||||
cursorChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Texture2D normalTexture;
|
|
||||||
protected Texture2D customTexture;
|
|
||||||
|
|
||||||
public BrowserCursor() {
|
|
||||||
Load();
|
|
||||||
|
|
||||||
normalTexture = new Texture2D(size, size, TextureFormat.ARGB32, false);
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
normalTexture.alphaIsTransparency = true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SetActiveCursor(BrowserNative.CursorType.Pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Switches the active cursor type. After calling this you can access the cursor image through this.Texture.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
public virtual void SetActiveCursor(CursorType type) {
|
|
||||||
if (type == CursorType.Custom) throw new ArgumentException("Use SetCustomCursor to set custom cursors.", "type");
|
|
||||||
|
|
||||||
if (type == CursorType.None) {
|
|
||||||
Texture = null;
|
|
||||||
//Side note: if you copy down a bunch of transparent pixels and try to set the mouse cursor to that
|
|
||||||
//both OS X and Windows fail to do what you'd expect.
|
|
||||||
//Edit: OS X is now crashing for me if you try to do that.
|
|
||||||
cursorChange();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = mapping[type];
|
|
||||||
var pixelData = allCursors.GetPixels(info.atlasOffset * size, 0, size, size);
|
|
||||||
|
|
||||||
Hotspot = info.hotspot;
|
|
||||||
|
|
||||||
normalTexture.SetPixels(pixelData);
|
|
||||||
|
|
||||||
normalTexture.Apply(true);
|
|
||||||
|
|
||||||
Texture = normalTexture;
|
|
||||||
|
|
||||||
cursorChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a custom cursor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cursor">ARGB texture to set</param>
|
|
||||||
/// <param name="hotspot"></param>
|
|
||||||
public virtual void SetCustomCursor(Texture2D cursor, Vector2 hotspot) {
|
|
||||||
var pixels = cursor.GetPixels32();
|
|
||||||
|
|
||||||
//First off, is it completely blank? 'Cuz if so that can cause OS X to crash.
|
|
||||||
var hasData = false;
|
|
||||||
for (int i = 0; i < pixels.Length; i++) {
|
|
||||||
if (pixels[i].a != 0) {
|
|
||||||
hasData = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasData) {
|
|
||||||
//it's blank, so handle it like a regular blank cursor
|
|
||||||
SetActiveCursor(CursorType.None);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!customTexture || customTexture.width != cursor.width || customTexture.height != cursor.height) {
|
|
||||||
Object.Destroy(customTexture);
|
|
||||||
customTexture = new Texture2D(cursor.width, cursor.height, TextureFormat.ARGB32, false);
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
customTexture.alphaIsTransparency = true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
customTexture.SetPixels32(pixels);
|
|
||||||
customTexture.Apply(true);
|
|
||||||
|
|
||||||
this.Hotspot = hotspot;
|
|
||||||
|
|
||||||
Texture = customTexture;
|
|
||||||
cursorChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: d74b3f6909bb41f4c86d03a80156416f
|
|
||||||
timeCreated: 1448061720
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,335 +0,0 @@
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
#define ZF_OSX
|
|
||||||
#endif
|
|
||||||
#if UNITY_EDITOR_LINX || UNITY_STANDALONE_LINUX
|
|
||||||
#define ZF_LINUX
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/** Helper class for reading data from an IUIHandler, converting it, and feeding it to the native backend. */
|
|
||||||
internal class BrowserInput {
|
|
||||||
|
|
||||||
private readonly Browser browser;
|
|
||||||
|
|
||||||
public BrowserInput(Browser browser) {
|
|
||||||
this.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool kbWasFocused = false;
|
|
||||||
private bool mouseWasFocused = false;
|
|
||||||
|
|
||||||
public void HandleInput() {
|
|
||||||
browser.UIHandler.InputUpdate();
|
|
||||||
bool focusChanged = false;
|
|
||||||
|
|
||||||
if (browser.UIHandler.MouseHasFocus || mouseWasFocused) {
|
|
||||||
HandleMouseInput();
|
|
||||||
}
|
|
||||||
if (browser.UIHandler.MouseHasFocus != mouseWasFocused) {
|
|
||||||
browser.UIHandler.BrowserCursor.HasMouse = browser.UIHandler.MouseHasFocus;
|
|
||||||
focusChanged = true;
|
|
||||||
}
|
|
||||||
mouseWasFocused = browser.UIHandler.MouseHasFocus;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (kbWasFocused != browser.UIHandler.KeyboardHasFocus) focusChanged = true;
|
|
||||||
|
|
||||||
if (browser.UIHandler.KeyboardHasFocus) {
|
|
||||||
if (!kbWasFocused) {
|
|
||||||
BrowserNative.zfb_setFocused(browser.browserId, kbWasFocused = true);
|
|
||||||
}
|
|
||||||
HandleKeyInput();
|
|
||||||
} else {
|
|
||||||
if (kbWasFocused) {
|
|
||||||
BrowserNative.zfb_setFocused(browser.browserId, kbWasFocused = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (focusChanged) {
|
|
||||||
browser._RaiseFocusEvent(browser.UIHandler.MouseHasFocus, browser.UIHandler.KeyboardHasFocus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HashSet<KeyCode> keysToReleaseOnFocusLoss = new HashSet<KeyCode>();
|
|
||||||
public List<Event> extraEventsToInject = new List<Event>();
|
|
||||||
|
|
||||||
private MouseButton prevButtons = 0;
|
|
||||||
private Vector2 prevPos;
|
|
||||||
|
|
||||||
private class ButtonHistory {
|
|
||||||
public float lastPressTime;
|
|
||||||
public int repeatCount;
|
|
||||||
public Vector3 lastPosition;
|
|
||||||
|
|
||||||
public void ButtonPress(Vector3 mousePos, IBrowserUI uiHandler, Vector2 browserSize) {
|
|
||||||
var now = Time.realtimeSinceStartup;
|
|
||||||
|
|
||||||
if (now - lastPressTime > uiHandler.InputSettings.multiclickSpeed) {
|
|
||||||
//too long ago? forget the past
|
|
||||||
repeatCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repeatCount > 0) {
|
|
||||||
//close enough to be a multiclick?
|
|
||||||
var p1 = Vector2.Scale(mousePos, browserSize);
|
|
||||||
var p2 = Vector2.Scale(lastPosition, browserSize);
|
|
||||||
if (Vector2.Distance(p1, p2) > uiHandler.InputSettings.multiclickTolerance) {
|
|
||||||
repeatCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repeatCount++;
|
|
||||||
|
|
||||||
lastPressTime = now;
|
|
||||||
lastPosition = mousePos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ButtonHistory leftClickHistory = new ButtonHistory();
|
|
||||||
|
|
||||||
private void HandleMouseInput() {
|
|
||||||
var handler = browser.UIHandler;
|
|
||||||
var mousePos = handler.MousePosition;
|
|
||||||
|
|
||||||
var currentButtons = handler.MouseButtons;
|
|
||||||
var mouseScroll = handler.MouseScroll;
|
|
||||||
|
|
||||||
if (mousePos != prevPos) {
|
|
||||||
BrowserNative.zfb_mouseMove(browser.browserId, mousePos.x, 1 - mousePos.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
FeedScrolling(mouseScroll, handler.InputSettings.scrollSpeed);
|
|
||||||
|
|
||||||
var leftChange = (prevButtons & MouseButton.Left) != (currentButtons & MouseButton.Left);
|
|
||||||
var leftDown = (currentButtons & MouseButton.Left) == MouseButton.Left;
|
|
||||||
var middleChange = (prevButtons & MouseButton.Middle) != (currentButtons & MouseButton.Middle);
|
|
||||||
var middleDown = (currentButtons & MouseButton.Middle) == MouseButton.Middle;
|
|
||||||
var rightChange = (prevButtons & MouseButton.Right) != (currentButtons & MouseButton.Right);
|
|
||||||
var rightDown = (currentButtons & MouseButton.Right) == MouseButton.Right;
|
|
||||||
|
|
||||||
if (leftChange) {
|
|
||||||
if (leftDown) leftClickHistory.ButtonPress(mousePos, handler, browser.Size);
|
|
||||||
BrowserNative.zfb_mouseButton(
|
|
||||||
browser.browserId, BrowserNative.MouseButton.MBT_LEFT, leftDown,
|
|
||||||
leftDown ? leftClickHistory.repeatCount : 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (middleChange) {
|
|
||||||
//no double-clicks, to be consistent with other browsers
|
|
||||||
BrowserNative.zfb_mouseButton(
|
|
||||||
browser.browserId, BrowserNative.MouseButton.MBT_MIDDLE, middleDown, 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (rightChange) {
|
|
||||||
//no double-clicks, to be consistent with other browsers
|
|
||||||
BrowserNative.zfb_mouseButton(
|
|
||||||
browser.browserId, BrowserNative.MouseButton.MBT_RIGHT, rightDown, 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevPos = mousePos;
|
|
||||||
prevButtons = currentButtons;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Vector2 accumulatedScroll;
|
|
||||||
private float lastScrollEvent;
|
|
||||||
/// <summary>
|
|
||||||
/// How often (in sec) we can send scroll events to the browser without it choking up.
|
|
||||||
/// The right number seems to depend on how hard the page is to render, so there's not a perfect number.
|
|
||||||
/// Hopefully this one works well, though.
|
|
||||||
/// </summary>
|
|
||||||
private const float maxScrollEventRate = 1 / 15f;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feeds scroll events to the browser.
|
|
||||||
/// In particular, it will clump together scrolling "floods" into fewer larger scrolls
|
|
||||||
/// to prevent the backend from getting choked up and taking forever to execute the requests.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mouseScroll"></param>
|
|
||||||
private void FeedScrolling(Vector2 mouseScroll, float scrollSpeed) {
|
|
||||||
accumulatedScroll += mouseScroll * scrollSpeed;
|
|
||||||
|
|
||||||
if (accumulatedScroll.sqrMagnitude != 0 && Time.realtimeSinceStartup > lastScrollEvent + maxScrollEventRate) {
|
|
||||||
//Debug.Log("Do scroll: " + accumulatedScroll);
|
|
||||||
|
|
||||||
//The backend seems to have trouble coping with horizontal AND vertical scroll. So only do one at a time.
|
|
||||||
//(And if we do both at once, vertical appears to get priority and horizontal gets ignored.)
|
|
||||||
|
|
||||||
if (Mathf.Abs(accumulatedScroll.x) > Mathf.Abs(accumulatedScroll.y)) {
|
|
||||||
BrowserNative.zfb_mouseScroll(browser.browserId, (int)accumulatedScroll.x, 0);
|
|
||||||
accumulatedScroll.x = 0;
|
|
||||||
accumulatedScroll.y = Mathf.Round(accumulatedScroll.y * .5f);//reduce the thing we weren't doing so it's less likely to accumulate strange
|
|
||||||
} else {
|
|
||||||
BrowserNative.zfb_mouseScroll(browser.browserId, 0, (int)accumulatedScroll.y);
|
|
||||||
accumulatedScroll.x = Mathf.Round(accumulatedScroll.x * .5f);
|
|
||||||
accumulatedScroll.y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastScrollEvent = Time.realtimeSinceStartup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleKeyInput() {
|
|
||||||
var keyEvents = browser.UIHandler.KeyEvents;
|
|
||||||
if (keyEvents.Count > 0) HandleKeyInput(keyEvents);
|
|
||||||
|
|
||||||
if (extraEventsToInject.Count > 0) {
|
|
||||||
HandleKeyInput(extraEventsToInject);
|
|
||||||
extraEventsToInject.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleKeyInput(List<Event> keyEvents) {
|
|
||||||
|
|
||||||
#if ZF_OSX
|
|
||||||
ReconstructInputs(keyEvents);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
foreach (var ev in keyEvents) {
|
|
||||||
var keyCode = KeyMappings.GetWindowsKeyCode(ev);
|
|
||||||
if (ev.character == '\n') ev.character = '\r';//'cuz that's what Chromium expects
|
|
||||||
|
|
||||||
if (ev.character == 0) {
|
|
||||||
if (ev.type == EventType.KeyDown) keysToReleaseOnFocusLoss.Add(ev.keyCode);
|
|
||||||
else keysToReleaseOnFocusLoss.Remove(ev.keyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (false) {
|
|
||||||
// if (ev.character != 0) Debug.Log("k >>> " + ev.character);
|
|
||||||
// else if (ev.type == EventType.KeyUp) Debug.Log("k ^^^ " + ev.keyCode);
|
|
||||||
// else if (ev.type == EventType.KeyDown) Debug.Log("k vvv " + ev.keyCode);
|
|
||||||
// }
|
|
||||||
|
|
||||||
FireCommands(ev);
|
|
||||||
|
|
||||||
if (ev.character != 0 && ev.type == EventType.KeyDown) {
|
|
||||||
#if ZF_LINUX
|
|
||||||
//It seems, on Linux, we don't get keydown, keypress, keyup, we just get a keypress, keyup.
|
|
||||||
//So, fire the keydown just before the keypress.
|
|
||||||
BrowserNative.zfb_keyEvent(browser.browserId, true, keyCode);
|
|
||||||
//Thanks for being consistent, Unity.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
BrowserNative.zfb_characterEvent(browser.browserId, ev.character, keyCode);
|
|
||||||
} else {
|
|
||||||
BrowserNative.zfb_keyEvent(browser.browserId, ev.type == EventType.KeyDown, keyCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleFocusLoss() {
|
|
||||||
foreach (var keyCode in keysToReleaseOnFocusLoss) {
|
|
||||||
//Debug.Log("Key " + keyCode + " is held, release");
|
|
||||||
var wCode = KeyMappings.GetWindowsKeyCode(new Event() { keyCode = keyCode });
|
|
||||||
BrowserNative.zfb_keyEvent(browser.browserId, false, wCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
keysToReleaseOnFocusLoss.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ZF_OSX
|
|
||||||
/** Used by ReconstructInputs */
|
|
||||||
protected HashSet<KeyCode> pressedKeys = new HashSet<KeyCode>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OS X + Unity has issues.
|
|
||||||
*
|
|
||||||
* Mac editor: If I press cmd+A: The "keydown A" event doesn't get sent,
|
|
||||||
* though we do get a keypress A and a keyup A.
|
|
||||||
* Mac player: We get duplicate keyUPs normally. If cmd is down we get duplicate keyDOWNs instead.
|
|
||||||
*/
|
|
||||||
protected void ReconstructInputs(List<Event> keyEvents) {
|
|
||||||
for (int i = 0; i < keyEvents.Count; ++i) {//int loop, not iterator, we mutate during iteration
|
|
||||||
var ev = keyEvents[i];
|
|
||||||
|
|
||||||
if (ev.type == EventType.KeyDown && ev.character == 0) {
|
|
||||||
pressedKeys.Add(ev.keyCode);
|
|
||||||
|
|
||||||
//Repeated keydown events sent in the same frame are always bogus (but valid if in different
|
|
||||||
//frames for held key repeats)
|
|
||||||
//Remove duplicated key down events in this tick.
|
|
||||||
for (int j = i + 1; j < keyEvents.Count; ++j) {
|
|
||||||
if (keyEvents[j].Equals(ev)) keyEvents.RemoveAt(j--);
|
|
||||||
}
|
|
||||||
} else if (ev.type == EventType.KeyDown) {
|
|
||||||
//key down with character.
|
|
||||||
//...did the key actually get pressed, though?
|
|
||||||
if (ev.keyCode != KeyCode.None && !pressedKeys.Contains(ev.keyCode)) {
|
|
||||||
//no. insert a keydown before the press
|
|
||||||
var downEv = new Event(ev) {
|
|
||||||
type = EventType.KeyDown,
|
|
||||||
character = (char)0
|
|
||||||
};
|
|
||||||
keyEvents.Insert(i++, downEv);
|
|
||||||
pressedKeys.Add(ev.keyCode);
|
|
||||||
}
|
|
||||||
} else if (ev.type == EventType.KeyUp) {
|
|
||||||
if (!pressedKeys.Contains(ev.keyCode)) {
|
|
||||||
//Ignore duplicate key up events
|
|
||||||
keyEvents.RemoveAt(i--);
|
|
||||||
}
|
|
||||||
|
|
||||||
pressedKeys.Remove(ev.keyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OS X + Unity has issues.
|
|
||||||
* Commands won't be run if the command is not in the application menu.
|
|
||||||
* Here we trap keystrokes and manually fire the relevant events in the browser.
|
|
||||||
*
|
|
||||||
* Also, ctrl+A stopped working with CEF at some point on Windows.
|
|
||||||
*/
|
|
||||||
protected void FireCommands(Event ev) {
|
|
||||||
|
|
||||||
#if ZF_OSX
|
|
||||||
if (ev.type != EventType.KeyDown || ev.character != 0 || !ev.command) return;
|
|
||||||
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case KeyCode.C:
|
|
||||||
browser.SendFrameCommand(BrowserNative.FrameCommand.Copy);
|
|
||||||
break;
|
|
||||||
case KeyCode.X:
|
|
||||||
browser.SendFrameCommand(BrowserNative.FrameCommand.Cut);
|
|
||||||
break;
|
|
||||||
case KeyCode.V:
|
|
||||||
browser.SendFrameCommand(BrowserNative.FrameCommand.Paste);
|
|
||||||
break;
|
|
||||||
case KeyCode.A:
|
|
||||||
browser.SendFrameCommand(BrowserNative.FrameCommand.SelectAll);
|
|
||||||
break;
|
|
||||||
case KeyCode.Z:
|
|
||||||
if (ev.shift) browser.SendFrameCommand(BrowserNative.FrameCommand.Redo);
|
|
||||||
else browser.SendFrameCommand(BrowserNative.FrameCommand.Undo);
|
|
||||||
break;
|
|
||||||
case KeyCode.Y:
|
|
||||||
//I, for one, prefer Y for redo, but shift+Z is more idiomatic on OS X
|
|
||||||
//Support both.
|
|
||||||
browser.SendFrameCommand(BrowserNative.FrameCommand.Redo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
//mmm, yeah. I guess Unity doesn't send us the keydown on a ctrl+a keystroke anymore.
|
|
||||||
if (ev.type != EventType.KeyUp || !ev.control) return;
|
|
||||||
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case KeyCode.A:
|
|
||||||
browser.SendFrameCommand(BrowserNative.FrameCommand.SelectAll);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: d00cb9636aac20d4989efeaf7de81909
|
|
||||||
timeCreated: 1450218467
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 78ee81db88d224e40843c8e826697dee
|
|
||||||
timeCreated: 1447282504
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,28 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper class for setting options on the browser system as a whole berfore the
|
|
||||||
/// backend initalizes.
|
|
||||||
/// </summary>
|
|
||||||
public class BrowserSystemSettings : MonoBehaviour {
|
|
||||||
[Tooltip("Where should we save the user's web profile (cookies, localStorage, etc.)? Leave blank to forget every times we restart.")]
|
|
||||||
public string profilePath;
|
|
||||||
|
|
||||||
[Tooltip("What user agent should we send to web pages when we request sites? Leave blank to use the default.")]
|
|
||||||
public string userAgent;
|
|
||||||
|
|
||||||
public void Awake() {
|
|
||||||
if (!string.IsNullOrEmpty(profilePath)) {
|
|
||||||
BrowserNative.ProfilePath = profilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(userAgent)) {
|
|
||||||
UserAgent.SetUserAgent(userAgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 731482151f2ac2041ab65282156b1664
|
|
||||||
timeCreated: 1510797762
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,9 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: e460e45c0d5976544a2af3c86b00bdb9
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1453258062
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,31 +0,0 @@
|
||||||
using System.Collections;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// These classes handle rendering the cursor of a browser.
|
|
||||||
///
|
|
||||||
/// Using one is optional. You can opt not to show a cursor.
|
|
||||||
/// </summary>
|
|
||||||
[RequireComponent(typeof(PointerUIBase))]
|
|
||||||
abstract public class CursorRendererBase : MonoBehaviour {
|
|
||||||
protected BrowserCursor cursor;
|
|
||||||
|
|
||||||
public virtual void OnEnable() {
|
|
||||||
StartCoroutine(Setup());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerator Setup() {
|
|
||||||
if (cursor != null) yield break;
|
|
||||||
|
|
||||||
yield return null;//wait a frame to let the browser UI get set up
|
|
||||||
|
|
||||||
cursor = GetComponent<Browser>().UIHandler.BrowserCursor;
|
|
||||||
cursor.cursorChange += CursorChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void CursorChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 30820dfc981b58e48a678a9b0e422fef
|
|
||||||
timeCreated: 1511217990
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,42 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles rendering a browser's cursor by letting using t he OS-level mouse cursor
|
|
||||||
/// (and changing it as needed).
|
|
||||||
/// </summary>
|
|
||||||
public class CursorRendererOS : CursorRendererBase {
|
|
||||||
[Tooltip("If true, the mouse cursor should be visible when it's not on a browser.")]
|
|
||||||
public bool cursorNormallyVisible = true;
|
|
||||||
|
|
||||||
protected override void CursorChange() {
|
|
||||||
if (!cursor.HasMouse) {
|
|
||||||
//no browser, do default
|
|
||||||
Cursor.visible = cursorNormallyVisible;
|
|
||||||
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
|
|
||||||
} else {
|
|
||||||
if (cursor.Texture != null) {
|
|
||||||
//browser, show this cursor
|
|
||||||
Cursor.visible = true;
|
|
||||||
Cursor.SetCursor(
|
|
||||||
cursor.Texture, cursor.Hotspot,
|
|
||||||
#if UNITY_STANDALONE_OSX
|
|
||||||
//Not sure why, but we get really ugly looking "garbled" shadows around the mouse cursor.
|
|
||||||
//I hate latency, but a software cursor is probably less irritating than looking at
|
|
||||||
//that ugly stuff.
|
|
||||||
CursorMode.ForceSoftware
|
|
||||||
#else
|
|
||||||
CursorMode.Auto
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
//browser, so no cursor
|
|
||||||
Cursor.visible = false;
|
|
||||||
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b27bad8d50722bb4083b964da1d1cae4
|
|
||||||
timeCreated: 1511217990
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,30 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Renders a browser's cursor by rendering something in the center of the screen.
|
|
||||||
/// </summary>
|
|
||||||
public class CursorRendererOverlay : CursorRendererBase {
|
|
||||||
|
|
||||||
[Tooltip("How large should we render the cursor?")]
|
|
||||||
public float scale = .5f;
|
|
||||||
|
|
||||||
protected override void CursorChange() {}
|
|
||||||
|
|
||||||
public void OnGUI() {
|
|
||||||
if (cursor == null) return;
|
|
||||||
|
|
||||||
if (!cursor.HasMouse || !cursor.Texture) return;
|
|
||||||
|
|
||||||
var tex = cursor.Texture;
|
|
||||||
|
|
||||||
var pos = new Rect(Screen.width / 2f, Screen.height / 2f, tex.width * scale, tex.height * scale);
|
|
||||||
pos.x -= cursor.Hotspot.x * scale;
|
|
||||||
pos.y -= cursor.Hotspot.y * scale;
|
|
||||||
|
|
||||||
GUI.DrawTexture(pos, tex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 62aba41510529884eaf9bdaa450168d7
|
|
||||||
timeCreated: 1511217990
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,81 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Renders a browser's cursor by add polygons to the scene offset from browser's face.
|
|
||||||
/// </summary>
|
|
||||||
public class CursorRendererWorldSpace : CursorRendererBase {
|
|
||||||
[Tooltip("How far to keep the cursor from the surface of the browser. Set it as low as you can without causing z-fighting." +
|
|
||||||
" (Note: The default cursor material will always render on top of everything, this is more useful if you use a different material.")]
|
|
||||||
public float zOffset = .005f;
|
|
||||||
[Tooltip("How large should the cursor be when rendered? (meters)")]
|
|
||||||
public float size = .1f;
|
|
||||||
|
|
||||||
private GameObject cursorHolder, cursorImage;
|
|
||||||
private PointerUIBase pointerUI;
|
|
||||||
|
|
||||||
private bool cursorVisible;
|
|
||||||
|
|
||||||
public void Awake() {
|
|
||||||
|
|
||||||
pointerUI = GetComponent<PointerUIBase>();
|
|
||||||
|
|
||||||
cursorHolder = new GameObject("Cursor for " + name);
|
|
||||||
cursorHolder.transform.localScale = new Vector3(size, size, size);
|
|
||||||
|
|
||||||
//If we place it in the parent, the scale can get wonky in some cases.
|
|
||||||
//so don't cursorHolder.transform.parent = transform;
|
|
||||||
|
|
||||||
cursorImage = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
|
||||||
cursorImage.name = "Cursor Image";
|
|
||||||
cursorImage.transform.parent = cursorHolder.transform;
|
|
||||||
var mr = cursorImage.GetComponent<MeshRenderer>();
|
|
||||||
mr.sharedMaterial = Resources.Load<Material>("Browser/CursorDecal");
|
|
||||||
Destroy(cursorImage.GetComponent<Collider>());
|
|
||||||
cursorImage.transform.SetParent(cursorHolder.transform, false);
|
|
||||||
cursorImage.transform.localScale = new Vector3(1, 1, 1);
|
|
||||||
|
|
||||||
|
|
||||||
cursorHolder.SetActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CursorChange() {
|
|
||||||
if (cursor.HasMouse && cursor.Texture) {
|
|
||||||
cursorVisible = true;
|
|
||||||
|
|
||||||
var cursorRenderer = cursorImage.GetComponent<Renderer>();
|
|
||||||
cursorRenderer.material.mainTexture = cursor.Texture;
|
|
||||||
|
|
||||||
var hs = cursor.Hotspot;
|
|
||||||
cursorRenderer.transform.localPosition = new Vector3(
|
|
||||||
.5f - hs.x / cursor.Texture.width,
|
|
||||||
-.5f + hs.y / cursor.Texture.height,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
cursorVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LateUpdate() {
|
|
||||||
Vector3 pos;
|
|
||||||
Quaternion rot;
|
|
||||||
pointerUI.GetCurrentHitLocation(out pos, out rot);
|
|
||||||
|
|
||||||
if (float.IsNaN(pos.x)) {
|
|
||||||
cursorHolder.SetActive(false);
|
|
||||||
} else {
|
|
||||||
cursorHolder.SetActive(cursorVisible);
|
|
||||||
|
|
||||||
cursorHolder.transform.position = pos + rot * new Vector3(0, 0, -zOffset);
|
|
||||||
cursorHolder.transform.rotation = rot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDestroy() {
|
|
||||||
Destroy(cursorHolder.gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b0f8a9c4898c5a644a048df52d9472fd
|
|
||||||
timeCreated: 1511217990
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,101 +0,0 @@
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum MouseButton {
|
|
||||||
Left = 0x1,
|
|
||||||
Middle = 0x2,
|
|
||||||
Right = 0x4,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BrowserInputSettings {
|
|
||||||
/**
|
|
||||||
* How fast do we scroll?
|
|
||||||
*/
|
|
||||||
public int scrollSpeed = 120;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How far can the cursor wander from its position before won't consider another click as a double/triple click?
|
|
||||||
* Value is number of pixels in browser space.
|
|
||||||
*/
|
|
||||||
public float multiclickTolerance = 6;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long must we wait between clicks before we don't consider it a double/triple/etc. click?
|
|
||||||
* Measured in seconds.
|
|
||||||
*/
|
|
||||||
public float multiclickSpeed = .7f;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proxy for browser input (and current mouse cursor).
|
|
||||||
* You can create your own implementation to take input however you'd like. To use your implementation,
|
|
||||||
* create a new instance and assign it to browser.UIHandler just after creating the browser.
|
|
||||||
*/
|
|
||||||
public interface IBrowserUI {
|
|
||||||
|
|
||||||
/** Called once per frame by the browser before fetching properties. */
|
|
||||||
void InputUpdate();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the browser will be getting mouse events. Typically this is true when the mouse if over the browser.
|
|
||||||
*
|
|
||||||
* If this is false, the Mouse* properties will be ignored.
|
|
||||||
*/
|
|
||||||
bool MouseHasFocus { get; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current mouse position.
|
|
||||||
*
|
|
||||||
* Returns the current position of the mouse with (0, 0) in the bottom-left corner and (1, 1) in the
|
|
||||||
* top-right corner.
|
|
||||||
*/
|
|
||||||
Vector2 MousePosition { get; }
|
|
||||||
|
|
||||||
/** Bitmask of currently depressed mouse buttons */
|
|
||||||
MouseButton MouseButtons { get; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delta X and Y scroll values since the last time InputUpdate() was called.
|
|
||||||
*
|
|
||||||
* Return 1 for every "click" of the scroll wheel, or smaller numbers for more incremental scrolling.
|
|
||||||
*/
|
|
||||||
Vector2 MouseScroll { get; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when the browser will receive keyboard events.
|
|
||||||
*
|
|
||||||
* In the simplest case, return the same value as MouseHasFocus, but you can track focus yourself if desired.
|
|
||||||
*
|
|
||||||
* If this is false, the Key* properties will be ignored.
|
|
||||||
*/
|
|
||||||
bool KeyboardHasFocus { get; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of key up/down events that have happened since the last InputUpdate() call.
|
|
||||||
*
|
|
||||||
* The returned list is not to be altered or retained.
|
|
||||||
*/
|
|
||||||
List<Event> KeyEvents { get; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a BrowserCursor instance. The Browser will update the current cursor to reflect the
|
|
||||||
* mouse's position on the page.
|
|
||||||
*
|
|
||||||
* The IBrowserUI is responsible for changing the actual cursor, be it the mouse cursor or some in-game display.
|
|
||||||
*/
|
|
||||||
BrowserCursor BrowserCursor { get; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* These settings are used to interpret the input data.
|
|
||||||
*/
|
|
||||||
BrowserInputSettings InputSettings { get; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 387f33fa3a8867e4fad235ad24e7fc95
|
|
||||||
timeCreated: 1448050780
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,112 +0,0 @@
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
#define ZF_OSX
|
|
||||||
#endif
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
/// <summary>
|
|
||||||
/// Helper class for IBrowserUI implementations for getting/generating keyboard events for sending to an IBrowserUI.
|
|
||||||
/// </summary>
|
|
||||||
public class KeyEvents {
|
|
||||||
|
|
||||||
|
|
||||||
/** Fills up with key events as they happen. */
|
|
||||||
protected List<Event> keyEvents = new List<Event>();
|
|
||||||
|
|
||||||
/** Swaps with keyEvents on InputUpdate and is returned in the main getter. */
|
|
||||||
protected List<Event> keyEventsLast = new List<Event>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// After calling InputUpdate, contains the key events to send to the browser.
|
|
||||||
/// </summary>
|
|
||||||
public List<Event> Events {
|
|
||||||
get { return keyEventsLast; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** List of keys Unity won't give us events for. So we have to poll. */
|
|
||||||
static readonly KeyCode[] keysToCheck = {
|
|
||||||
#if ZF_OSX
|
|
||||||
//On windows you get GUI events for ctrl, super, alt. On mac...you don't!
|
|
||||||
KeyCode.LeftCommand,
|
|
||||||
KeyCode.RightCommand,
|
|
||||||
KeyCode.LeftControl,
|
|
||||||
KeyCode.RightControl,
|
|
||||||
KeyCode.LeftAlt,
|
|
||||||
KeyCode.RightAlt,
|
|
||||||
//KeyCode.CapsLock, unity refuses to inform us of this, so there's not much we can do
|
|
||||||
#endif
|
|
||||||
//Unity consistently doesn't send events for shift across all platforms.
|
|
||||||
KeyCode.LeftShift,
|
|
||||||
KeyCode.RightShift,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call once per frame before grabbing
|
|
||||||
/// </summary>
|
|
||||||
public void InputUpdate() {
|
|
||||||
//Note: keyEvents gets filled in OnGUI as things happen. InputUpdate get called just before it's read.
|
|
||||||
//To get the right events to the right place at the right time, swap the "double buffer" of key events.
|
|
||||||
var tmp = keyEvents;
|
|
||||||
keyEvents = keyEventsLast;
|
|
||||||
keyEventsLast = tmp;
|
|
||||||
keyEvents.Clear();
|
|
||||||
|
|
||||||
//Unity doesn't include events for some keys, so fake it by checking each frame.
|
|
||||||
for (int i = 0; i < keysToCheck.Length; i++) {
|
|
||||||
if (Input.GetKeyDown(keysToCheck[i])) {
|
|
||||||
//Prepend down, postpend up. We don't know which happened first, but pressing
|
|
||||||
//modifiers usually precedes other key presses and releasing tends to follow.
|
|
||||||
keyEventsLast.Insert(0, new Event() { type = EventType.KeyDown, keyCode = keysToCheck[i] });
|
|
||||||
} else if (Input.GetKeyUp(keysToCheck[i])) {
|
|
||||||
keyEventsLast.Add(new Event() { type = EventType.KeyUp, keyCode = keysToCheck[i] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Call this with any GUI events you get from Unity that you want to have passed to the browser.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ev"></param>
|
|
||||||
public void Feed(Event ev) {
|
|
||||||
if (ev.type != EventType.KeyDown && ev.type != EventType.KeyUp) return;
|
|
||||||
|
|
||||||
// if (ev.character != 0) Debug.Log("ev >>> " + ev.character);
|
|
||||||
// else if (ev.type == EventType.KeyUp) Debug.Log("ev ^^^ " + ev.keyCode);
|
|
||||||
// else if (ev.type == EventType.KeyDown) Debug.Log("ev vvv " + ev.keyCode);
|
|
||||||
|
|
||||||
keyEvents.Add(new Event(ev));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Injects a key press. Call Release laster to let go.
|
|
||||||
/// If the key you are pressing represents a character this may not type that character. Use Type() instead.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
public void Press(KeyCode key) {
|
|
||||||
keyEvents.Add(new Event {
|
|
||||||
type = EventType.KeyDown, keyCode = key
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Release(KeyCode key) {
|
|
||||||
keyEvents.Add(new Event {
|
|
||||||
type = EventType.KeyUp, keyCode = key
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Types the given text in. THis does not simulate pressing each key and releasing it.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text"></param>
|
|
||||||
public void Type(string text) {
|
|
||||||
for (int i = 0; i < text.Length; i++) {
|
|
||||||
//fixme: multibyte chars >16 bits
|
|
||||||
keyEvents.Add(new Event {
|
|
||||||
type = EventType.KeyDown, character = text[i],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 34df8173f4fc89a4a94045d4973f1dac
|
|
||||||
timeCreated: 1495912815
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,10 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: cab02f7db18ee9f41abc1d23c3818b09
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1511210876
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,200 +0,0 @@
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
#define ZF_OSX
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class will handle input to a browser based on the mouse position and a mesh collider on the browser.
|
|
||||||
* Mouse positions are looked up according to the UVs on the *collider* mesh. Generally, you will want to use
|
|
||||||
* the same or a visually similar (including UVs) mesh for the renderer.
|
|
||||||
*/
|
|
||||||
[Obsolete("Use PointerUIMesh instead.")]
|
|
||||||
public class ClickMeshBrowserUI : MonoBehaviour, IBrowserUI {
|
|
||||||
/**
|
|
||||||
* Creates a new UI handler.
|
|
||||||
* We will attach to {parent}, which must have the mesh we are interacting with.
|
|
||||||
* In most cases, this will also be the same object that has the Browser we will be fed to. (a la browser.UIHandler)
|
|
||||||
*/
|
|
||||||
public static ClickMeshBrowserUI Create(MeshCollider meshCollider) {
|
|
||||||
var ui = meshCollider.gameObject.AddComponent<ClickMeshBrowserUI>();
|
|
||||||
ui.meshCollider = meshCollider;
|
|
||||||
return ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Awake() {
|
|
||||||
BrowserCursor = new BrowserCursor();
|
|
||||||
BrowserCursor.cursorChange += CursorUpdated;
|
|
||||||
|
|
||||||
InputSettings = new BrowserInputSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MeshCollider meshCollider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How far can we reach to touch a browser?
|
|
||||||
*
|
|
||||||
* HideInInspector:
|
|
||||||
* Showing it in the inspector would imply that changing the value would be used, but in most practical cases
|
|
||||||
* with FPSBrowserUI, the value will be overridden by the FPSCursorRenderer.
|
|
||||||
*/
|
|
||||||
[HideInInspector]
|
|
||||||
public float maxDistance = float.PositiveInfinity;
|
|
||||||
|
|
||||||
/** Fills up with key events as they happen. */
|
|
||||||
protected List<Event> keyEvents = new List<Event>();
|
|
||||||
|
|
||||||
/** Swaps with keyEvents on InputUpdate and is returned in the main getter. */
|
|
||||||
protected List<Event> keyEventsLast = new List<Event>();
|
|
||||||
|
|
||||||
/** Returns the user's interacting ray, usually the mouse pointer in some form. */
|
|
||||||
protected virtual Ray LookRay {
|
|
||||||
get { return Camera.main.ScreenPointToRay(Input.mousePosition); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** List of keys Unity won't give us events for. So we have to poll. */
|
|
||||||
static readonly KeyCode[] keysToCheck = {
|
|
||||||
#if ZF_OSX
|
|
||||||
//On windows you get GUI events for ctrl, super, alt. On mac...you don't!
|
|
||||||
KeyCode.LeftCommand,
|
|
||||||
KeyCode.RightCommand,
|
|
||||||
KeyCode.LeftControl,
|
|
||||||
KeyCode.RightControl,
|
|
||||||
KeyCode.LeftAlt,
|
|
||||||
KeyCode.RightAlt,
|
|
||||||
//KeyCode.CapsLock, unity refuses to inform us of this, so there's not much we can do
|
|
||||||
#endif
|
|
||||||
//Unity consistently doesn't send events for shift across all platforms.
|
|
||||||
KeyCode.LeftShift,
|
|
||||||
KeyCode.RightShift,
|
|
||||||
};
|
|
||||||
|
|
||||||
public virtual void InputUpdate() {
|
|
||||||
//Note: keyEvents gets filled in OnGUI as things happen. InputUpdate get called just before it's read.
|
|
||||||
//To get the right events to the right place at the right time, swap the "double buffer" of key events.
|
|
||||||
var tmp = keyEvents;
|
|
||||||
keyEvents = keyEventsLast;
|
|
||||||
keyEventsLast = tmp;
|
|
||||||
keyEvents.Clear();
|
|
||||||
|
|
||||||
|
|
||||||
//Trace mouse from the main camera
|
|
||||||
var mouseRay = LookRay;
|
|
||||||
RaycastHit hit;
|
|
||||||
Physics.Raycast(mouseRay, out hit, maxDistance);
|
|
||||||
|
|
||||||
if (hit.transform != meshCollider.transform) {
|
|
||||||
//not looking at it.
|
|
||||||
MousePosition = new Vector3(0, 0);
|
|
||||||
MouseButtons = 0;
|
|
||||||
MouseScroll = new Vector2(0, 0);
|
|
||||||
|
|
||||||
MouseHasFocus = false;
|
|
||||||
KeyboardHasFocus = false;
|
|
||||||
|
|
||||||
LookOff();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LookOn();
|
|
||||||
MouseHasFocus = true;
|
|
||||||
KeyboardHasFocus = true;
|
|
||||||
|
|
||||||
//convert ray hit to useful mouse position on page
|
|
||||||
var localPoint = hit.textureCoord;
|
|
||||||
MousePosition = localPoint;
|
|
||||||
|
|
||||||
var buttons = (MouseButton)0;
|
|
||||||
if (Input.GetMouseButton(0)) buttons |= MouseButton.Left;
|
|
||||||
if (Input.GetMouseButton(1)) buttons |= MouseButton.Right;
|
|
||||||
if (Input.GetMouseButton(2)) buttons |= MouseButton.Middle;
|
|
||||||
MouseButtons = buttons;
|
|
||||||
|
|
||||||
MouseScroll = Input.mouseScrollDelta;
|
|
||||||
|
|
||||||
|
|
||||||
//Unity doesn't include events for some keys, so fake it by checking each frame.
|
|
||||||
for (int i = 0; i < keysToCheck.Length; i++) {
|
|
||||||
if (Input.GetKeyDown(keysToCheck[i])) {
|
|
||||||
//Prepend down, postpend up. We don't know which happened first, but pressing
|
|
||||||
//modifiers usually precedes other key presses and releasing tends to follow.
|
|
||||||
keyEventsLast.Insert(0, new Event() {type = EventType.KeyDown, keyCode = keysToCheck[i]});
|
|
||||||
} else if (Input.GetKeyUp(keysToCheck[i])) {
|
|
||||||
keyEventsLast.Add(new Event() {type = EventType.KeyUp, keyCode = keysToCheck[i]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGUI() {
|
|
||||||
var ev = Event.current;
|
|
||||||
if (ev.type != EventType.KeyDown && ev.type != EventType.KeyUp) return;
|
|
||||||
|
|
||||||
// if (ev.character != 0) Debug.Log("ev >>> " + ev.character);
|
|
||||||
// else if (ev.type == EventType.KeyUp) Debug.Log("ev ^^^ " + ev.keyCode);
|
|
||||||
// else if (ev.type == EventType.KeyDown) Debug.Log("ev vvv " + ev.keyCode);
|
|
||||||
|
|
||||||
keyEvents.Add(new Event(ev));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool mouseWasOver = false;
|
|
||||||
protected void LookOn() {
|
|
||||||
if (BrowserCursor != null) {
|
|
||||||
CursorUpdated();
|
|
||||||
}
|
|
||||||
mouseWasOver = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void LookOff() {
|
|
||||||
if (BrowserCursor != null && mouseWasOver) {
|
|
||||||
SetCursor(null);
|
|
||||||
}
|
|
||||||
mouseWasOver = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void CursorUpdated() {
|
|
||||||
SetCursor(BrowserCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current mouse cursor.
|
|
||||||
* If the cursor is null we are not looking at this browser.
|
|
||||||
*
|
|
||||||
* This base implementation changes the mouse cursor, but you could change an in-game reticle, etc.
|
|
||||||
*/
|
|
||||||
protected virtual void SetCursor(BrowserCursor newCursor) {
|
|
||||||
//note that HandleKeyInputBrowserCursor can change while we don't have focus.
|
|
||||||
//In such a case, don't do anything
|
|
||||||
if (!MouseHasFocus && newCursor != null) return;
|
|
||||||
|
|
||||||
if (newCursor == null) {
|
|
||||||
Cursor.visible = true;
|
|
||||||
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
|
|
||||||
} else {
|
|
||||||
if (newCursor.Texture != null) {
|
|
||||||
Cursor.visible = true;
|
|
||||||
Cursor.SetCursor(newCursor.Texture, newCursor.Hotspot, CursorMode.Auto);
|
|
||||||
} else {
|
|
||||||
Cursor.visible = false;
|
|
||||||
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MouseHasFocus { get; protected set; }
|
|
||||||
public Vector2 MousePosition { get; protected set; }
|
|
||||||
public MouseButton MouseButtons { get; protected set; }
|
|
||||||
public Vector2 MouseScroll { get; protected set; }
|
|
||||||
public bool KeyboardHasFocus { get; protected set; }
|
|
||||||
public List<Event> KeyEvents { get { return keyEventsLast; } }
|
|
||||||
public BrowserCursor BrowserCursor { get; protected set; }
|
|
||||||
public BrowserInputSettings InputSettings { get; protected set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: beafc338c0850714f8831f03b4ba9a67
|
|
||||||
timeCreated: 1450133185
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,62 +0,0 @@
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
/**
|
|
||||||
* Works like ClickMeshBrowserUI, but assumes you are pointing at buttons with your nose
|
|
||||||
* (a camera or object's transform.forward) instead of with a visible mouse pointer.
|
|
||||||
*
|
|
||||||
* This relies on the given FPSCursorRenderer to render the cursor.
|
|
||||||
*
|
|
||||||
* Unlike MeshColliderBrowserUI, this won't be used by default. If you'd like to use it,
|
|
||||||
* call CursorCrosshair.SetUpBrowserInput.
|
|
||||||
*
|
|
||||||
* As with MeshColliderBrowserUI, pass in the mesh we interact on to {meshCollider}.
|
|
||||||
*
|
|
||||||
* {worldPointer} is the object we are pointing with. Usually you can use Camera.main.transsform.
|
|
||||||
* Its world-space forward direction will be used to get the user's interaction ray.
|
|
||||||
*/
|
|
||||||
[RequireComponent(typeof(Browser))]
|
|
||||||
[RequireComponent(typeof(MeshCollider))]
|
|
||||||
[Obsolete("Use PointerUIMesh instead.")]
|
|
||||||
public class FPSBrowserUI : ClickMeshBrowserUI {
|
|
||||||
protected Transform worldPointer;
|
|
||||||
protected FPSCursorRenderer cursorRenderer;
|
|
||||||
|
|
||||||
public void Start() {
|
|
||||||
FPSCursorRenderer.SetUpBrowserInput(GetComponent<Browser>(), GetComponent<MeshCollider>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FPSBrowserUI Create(MeshCollider meshCollider, Transform worldPointer, FPSCursorRenderer cursorRenderer) {
|
|
||||||
var ui = meshCollider.gameObject.GetComponent<FPSBrowserUI>();
|
|
||||||
if (!ui) ui = meshCollider.gameObject.AddComponent<FPSBrowserUI>();
|
|
||||||
ui.meshCollider = meshCollider;
|
|
||||||
ui.worldPointer = worldPointer;
|
|
||||||
ui.cursorRenderer = cursorRenderer;
|
|
||||||
|
|
||||||
return ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Ray LookRay {
|
|
||||||
get { return new Ray(worldPointer.position, worldPointer.forward); }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void SetCursor(BrowserCursor newCursor) {
|
|
||||||
if (newCursor != null && !MouseHasFocus) return;
|
|
||||||
|
|
||||||
cursorRenderer.SetCursor(newCursor, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void InputUpdate() {
|
|
||||||
if (!cursorRenderer.EnableInput) {
|
|
||||||
MouseHasFocus = false;
|
|
||||||
KeyboardHasFocus = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.InputUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b336f8b13576fc5459369f2a394339d5
|
|
||||||
timeCreated: 1452987345
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,82 +0,0 @@
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a crosshair in the middle of the screen which changes to cursors as you mouseover
|
|
||||||
* things in world-space browsers.
|
|
||||||
*
|
|
||||||
* Often, this will be created automatically. If you want to alter parameters, add this script
|
|
||||||
* to an object (such as the camera) and edit them there.
|
|
||||||
*/
|
|
||||||
[Obsolete("Use PointerUIMesh and CursorRendererOverlay instead.")]
|
|
||||||
public class FPSCursorRenderer : MonoBehaviour {
|
|
||||||
private static FPSCursorRenderer _instance;
|
|
||||||
public static FPSCursorRenderer Instance {
|
|
||||||
get {
|
|
||||||
if (!_instance) {
|
|
||||||
_instance = FindObjectOfType<FPSCursorRenderer>();
|
|
||||||
if (!_instance) {
|
|
||||||
var go = new GameObject("Cursor Crosshair");
|
|
||||||
_instance = go.AddComponent<FPSCursorRenderer>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Tooltip("How large should we render the cursor?")]
|
|
||||||
public float scale = .5f;
|
|
||||||
|
|
||||||
[Tooltip("How far can we reach to push buttons and such?")]
|
|
||||||
public float maxDistance = 7f;
|
|
||||||
|
|
||||||
[Tooltip("What are we using to point at things? Leave as null to use Camera.main")]
|
|
||||||
public Transform pointer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle this to enable/disable input for all FPSBrowserUI objects.
|
|
||||||
* This is useful, for example, during plot sequences and pause menus.
|
|
||||||
*/
|
|
||||||
public bool EnableInput { get; set; }
|
|
||||||
|
|
||||||
public static void SetUpBrowserInput(Browser browser, MeshCollider mesh) {
|
|
||||||
var crossHair = Instance;
|
|
||||||
|
|
||||||
var pointer = crossHair.pointer;
|
|
||||||
if (!pointer) pointer = Camera.main.transform;//nb: don't use crossHair.pointer ?? camera, will incorrectly return null
|
|
||||||
var fpsUI = FPSBrowserUI.Create(mesh, pointer, crossHair);
|
|
||||||
fpsUI.maxDistance = crossHair.maxDistance;
|
|
||||||
browser.UIHandler = fpsUI;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BrowserCursor baseCursor, currentCursor;
|
|
||||||
|
|
||||||
public void Start() {
|
|
||||||
EnableInput = true;
|
|
||||||
baseCursor = new BrowserCursor();
|
|
||||||
baseCursor.SetActiveCursor(BrowserNative.CursorType.Cross);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGUI() {
|
|
||||||
if (!EnableInput) return;
|
|
||||||
|
|
||||||
var cursor = currentCursor ?? baseCursor;
|
|
||||||
var tex = cursor.Texture;
|
|
||||||
|
|
||||||
if (tex == null) return;//hidden cursor
|
|
||||||
|
|
||||||
var pos = new Rect(Screen.width / 2f, Screen.height / 2f, tex.width * scale, tex.height * scale);
|
|
||||||
pos.x -= cursor.Hotspot.x * scale;
|
|
||||||
pos.y -= cursor.Hotspot.y * scale;
|
|
||||||
|
|
||||||
GUI.DrawTexture(pos, tex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetCursor(BrowserCursor newCursor, FPSBrowserUI ui) {
|
|
||||||
currentCursor = newCursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 27abec923057368408f62c2ebf6d54b3
|
|
||||||
timeCreated: 1453246003
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,185 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/** Attach this script to a GUI Image to use a browser on it. */
|
|
||||||
[RequireComponent(typeof(RawImage))]
|
|
||||||
[RequireComponent(typeof(Browser))]
|
|
||||||
[Obsolete("Use PointerUIGUI and CursorRendererOS instead.")]
|
|
||||||
public class GUIBrowserUI :
|
|
||||||
MonoBehaviour,
|
|
||||||
IBrowserUI,
|
|
||||||
ISelectHandler, IDeselectHandler,
|
|
||||||
IPointerEnterHandler, IPointerExitHandler,
|
|
||||||
IPointerDownHandler
|
|
||||||
{
|
|
||||||
protected RawImage myImage;
|
|
||||||
protected Browser browser;
|
|
||||||
|
|
||||||
public bool enableInput = true, autoResize = true;
|
|
||||||
|
|
||||||
protected void Awake() {
|
|
||||||
BrowserCursor = new BrowserCursor();
|
|
||||||
InputSettings = new BrowserInputSettings();
|
|
||||||
|
|
||||||
browser = GetComponent<Browser>();
|
|
||||||
myImage = GetComponent<RawImage>();
|
|
||||||
|
|
||||||
browser.afterResize += UpdateTexture;
|
|
||||||
browser.UIHandler = this;
|
|
||||||
BrowserCursor.cursorChange += () => {
|
|
||||||
SetCursor(BrowserCursor);
|
|
||||||
};
|
|
||||||
|
|
||||||
rTransform = GetComponent<RectTransform>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnEnable() {
|
|
||||||
if (autoResize) StartCoroutine(WatchResize());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Automatically resizes the browser to match the size of this object. */
|
|
||||||
private IEnumerator WatchResize() {
|
|
||||||
Rect currentSize = new Rect();
|
|
||||||
|
|
||||||
while (enabled) {
|
|
||||||
var rect = rTransform.rect;
|
|
||||||
|
|
||||||
if (rect.size.x <= 0 || rect.size.y <= 0) rect.size = new Vector2(512, 512);
|
|
||||||
if (rect.size != currentSize.size) {
|
|
||||||
browser.Resize((int)rect.size.x, (int)rect.size.y);
|
|
||||||
currentSize = rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
//yield return new WaitForSeconds(.5f); won't work if you pause the game, which, BTW, is a great time to resize the screen ;-)
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void UpdateTexture(Texture2D texture) {
|
|
||||||
myImage.texture = texture;
|
|
||||||
myImage.uvRect = new Rect(0, 0, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<Event> keyEvents = new List<Event>();
|
|
||||||
protected List<Event> keyEventsLast = new List<Event>();
|
|
||||||
protected BaseRaycaster raycaster;
|
|
||||||
protected RectTransform rTransform;
|
|
||||||
// protected List<RaycastResult> raycastResults = new List<RaycastResult>();
|
|
||||||
|
|
||||||
public virtual void InputUpdate() {
|
|
||||||
var tmp = keyEvents;
|
|
||||||
keyEvents = keyEventsLast;
|
|
||||||
keyEventsLast = tmp;
|
|
||||||
keyEvents.Clear();
|
|
||||||
|
|
||||||
if (MouseHasFocus) {
|
|
||||||
if (!raycaster) raycaster = GetComponentInParent<BaseRaycaster>();
|
|
||||||
|
|
||||||
// raycastResults.Clear();
|
|
||||||
|
|
||||||
// raycaster.Raycast(data, raycastResults);
|
|
||||||
|
|
||||||
// if (raycastResults.Count != 0) {
|
|
||||||
// Vector2 pos = raycastResults[0].stuff
|
|
||||||
Vector2 pos;
|
|
||||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
||||||
(RectTransform)transform, Input.mousePosition, raycaster.eventCamera, out pos
|
|
||||||
);
|
|
||||||
pos.x = pos.x / rTransform.rect.width + rTransform.pivot.x;
|
|
||||||
pos.y = pos.y / rTransform.rect.height + rTransform.pivot.y;
|
|
||||||
MousePosition = pos;
|
|
||||||
|
|
||||||
|
|
||||||
var buttons = (MouseButton)0;
|
|
||||||
if (Input.GetMouseButton(0)) buttons |= MouseButton.Left;
|
|
||||||
if (Input.GetMouseButton(1)) buttons |= MouseButton.Right;
|
|
||||||
if (Input.GetMouseButton(2)) buttons |= MouseButton.Middle;
|
|
||||||
MouseButtons = buttons;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MouseScroll = Input.mouseScrollDelta;
|
|
||||||
} else {
|
|
||||||
MouseButtons = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Unity doesn't include events for some keys, so fake it.
|
|
||||||
if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift)) {
|
|
||||||
//Note: doesn't matter if left or right shift, the browser can't tell.
|
|
||||||
//(Prepend event. We don't know what happened first, but pressing shift usually precedes other key presses)
|
|
||||||
keyEventsLast.Insert(0, new Event() { type = EventType.KeyDown, keyCode = KeyCode.LeftShift });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Input.GetKeyUp(KeyCode.LeftShift) || Input.GetKeyUp(KeyCode.RightShift)) {
|
|
||||||
//Note: doesn't matter if left or right shift, the browser can't tell.
|
|
||||||
keyEventsLast.Add(new Event() { type = EventType.KeyUp, keyCode = KeyCode.LeftShift });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGUI() {
|
|
||||||
var ev = Event.current;
|
|
||||||
if (ev.type != EventType.KeyDown && ev.type != EventType.KeyUp) return;
|
|
||||||
|
|
||||||
keyEvents.Add(new Event(ev));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void SetCursor(BrowserCursor newCursor) {
|
|
||||||
if (!_mouseHasFocus && newCursor != null) return;
|
|
||||||
|
|
||||||
if (newCursor == null) {
|
|
||||||
Cursor.visible = true;
|
|
||||||
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
|
|
||||||
} else {
|
|
||||||
if (newCursor.Texture != null) {
|
|
||||||
Cursor.visible = true;
|
|
||||||
Cursor.SetCursor(newCursor.Texture, newCursor.Hotspot, CursorMode.Auto);
|
|
||||||
} else {
|
|
||||||
Cursor.visible = false;
|
|
||||||
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool _mouseHasFocus;
|
|
||||||
public bool MouseHasFocus { get { return _mouseHasFocus && enableInput; } }
|
|
||||||
public Vector2 MousePosition { get; private set; }
|
|
||||||
public MouseButton MouseButtons { get; private set; }
|
|
||||||
public Vector2 MouseScroll { get; private set; }
|
|
||||||
protected bool _keyboardHasFocus;
|
|
||||||
public bool KeyboardHasFocus { get { return _keyboardHasFocus && enableInput; } }
|
|
||||||
public List<Event> KeyEvents { get { return keyEventsLast; } }
|
|
||||||
public BrowserCursor BrowserCursor { get; private set; }
|
|
||||||
public BrowserInputSettings InputSettings { get; private set; }
|
|
||||||
|
|
||||||
public void OnSelect(BaseEventData eventData) {
|
|
||||||
_keyboardHasFocus = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDeselect(BaseEventData eventData) {
|
|
||||||
_keyboardHasFocus = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPointerEnter(PointerEventData eventData) {
|
|
||||||
_mouseHasFocus = true;
|
|
||||||
SetCursor(BrowserCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPointerExit(PointerEventData eventData) {
|
|
||||||
_mouseHasFocus = false;
|
|
||||||
SetCursor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void OnPointerDown(PointerEventData eventData) {
|
|
||||||
EventSystem.current.SetSelectedGameObject(gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 312e08c685ba1ee4e96f0ff4128d6e49
|
|
||||||
timeCreated: 1453317757
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,434 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
#if UNITY_2017_2_OR_NEWER
|
|
||||||
using UnityEngine.XR;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base class that handles input for mouse/touch/pointer/VR/nose inputs.
|
|
||||||
/// The concept is thus:
|
|
||||||
/// You have an arbitrary number of 3D (FPS player, VR pointer, and world ray) and
|
|
||||||
/// 2D (mouse, touch, and screen position) pointers and you want any of them
|
|
||||||
/// to be able to interact with the Browser.
|
|
||||||
///
|
|
||||||
/// Concrete implementations of this class handle interacting with different rendered mediums
|
|
||||||
/// (such as a mesh or a GUI renderer).
|
|
||||||
/// </summary>
|
|
||||||
[RequireComponent(typeof(Browser))]
|
|
||||||
public abstract class PointerUIBase : MonoBehaviour, IBrowserUI {
|
|
||||||
public readonly KeyEvents keyEvents = new KeyEvents();
|
|
||||||
|
|
||||||
|
|
||||||
protected Browser browser;
|
|
||||||
protected bool appFocused = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called once per tick. Handlers registered here should look at the pointers they have
|
|
||||||
/// and tell us about them.
|
|
||||||
/// </summary>
|
|
||||||
public event Action onHandlePointers = () => {};
|
|
||||||
|
|
||||||
protected int currentPointerId;
|
|
||||||
|
|
||||||
protected readonly List<PointerState> currentPointers = new List<PointerState>();
|
|
||||||
|
|
||||||
[Tooltip(
|
|
||||||
"When clicking, how far (in browser-space pixels) must the cursor be moved before we send this movement to the browser backend? " +
|
|
||||||
"This helps keep us from unintentionally dragging when we meant to just click, esp. under VR where it's hard to hold the cursor still."
|
|
||||||
)]
|
|
||||||
public float dragMovementThreshold = 0;
|
|
||||||
/// <summary>
|
|
||||||
/// Cursor location when we most recently went from 0 buttons to any buttons down.
|
|
||||||
/// </summary>
|
|
||||||
protected Vector2 mouseDownPosition;
|
|
||||||
/// <summary>
|
|
||||||
/// True when we have the any mouse button down AND we've moved at least dragMovementThreshold after that.
|
|
||||||
/// </summary>
|
|
||||||
protected bool dragging = false;
|
|
||||||
|
|
||||||
#region Pointer Core
|
|
||||||
|
|
||||||
public struct PointerState {
|
|
||||||
/// <summary>
|
|
||||||
/// Unique value for this pointer, to distinguish it by. Must be > 0.
|
|
||||||
/// </summary>
|
|
||||||
public int id;
|
|
||||||
/// <summary>
|
|
||||||
/// Is the pointer a 2d or 3d pointer?
|
|
||||||
/// </summary>
|
|
||||||
public bool is2D;
|
|
||||||
public Vector2 position2D;
|
|
||||||
public Ray position3D;
|
|
||||||
/// <summary>
|
|
||||||
/// Currently depressed "buttons" on this pointer.
|
|
||||||
/// </summary>
|
|
||||||
public MouseButton activeButtons;
|
|
||||||
/// <summary>
|
|
||||||
/// If the pointer can scroll, delta scrolling values since last frame.
|
|
||||||
/// </summary>
|
|
||||||
public Vector2 scrollDelta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the browser gets clicked with any mouse button.
|
|
||||||
/// (More precisely, when we go from having no buttons down to 1+ buttons down.)
|
|
||||||
/// </summary>
|
|
||||||
public event Action onClick = () => {};
|
|
||||||
|
|
||||||
|
|
||||||
public virtual void Awake() {
|
|
||||||
BrowserCursor = new BrowserCursor();
|
|
||||||
BrowserCursor.cursorChange += CursorUpdated;
|
|
||||||
|
|
||||||
InputSettings = new BrowserInputSettings();
|
|
||||||
|
|
||||||
browser = GetComponent<Browser>();
|
|
||||||
browser.UIHandler = this;
|
|
||||||
|
|
||||||
|
|
||||||
onHandlePointers += OnHandlePointers;
|
|
||||||
|
|
||||||
if (disableMouseEmulation) Input.simulateMouseWithTouches = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void InputUpdate() {
|
|
||||||
keyEvents.InputUpdate();
|
|
||||||
|
|
||||||
p_currentDown = p_anyDown = p_currentOver = p_anyOver = -1;
|
|
||||||
currentPointers.Clear();
|
|
||||||
|
|
||||||
onHandlePointers();
|
|
||||||
|
|
||||||
CalculatePointer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnApplicationFocus(bool focused) {
|
|
||||||
appFocused = focused;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a 2D screen-space coordinate to browser-space coordinates.
|
|
||||||
/// If the given point doesn't map to the browser, return float.NaN for the position.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="screenPosition"></param>
|
|
||||||
/// <param name="pointerId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected abstract Vector2 MapPointerToBrowser(Vector2 screenPosition, int pointerId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a 3D world-space ray to a browser-space coordinate.
|
|
||||||
/// If the given ray doesn't map to the browser, return float.NaN for the position.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="worldRay"></param>
|
|
||||||
/// <param name="pointerId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected abstract Vector2 MapRayToBrowser(Ray worldRay, int pointerId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the current position+rotation of the active pointer in world space.
|
|
||||||
/// If there is none, or it doesn't make sense to map to world space, position will
|
|
||||||
/// be NaNs.
|
|
||||||
///
|
|
||||||
/// Coordinates are in world space. The rotation should point up in the direction the browser sees as up.
|
|
||||||
/// Z+ should go "into" the browser surface.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pos"></param>
|
|
||||||
/// <param name="rot"></param>
|
|
||||||
public abstract void GetCurrentHitLocation(out Vector3 pos, out Quaternion rot);
|
|
||||||
|
|
||||||
/** Indexes into currentPointers for useful items this frame. -1 if N/A. */
|
|
||||||
protected int p_currentDown, p_anyDown, p_currentOver, p_anyOver;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Feeds the state of the given pointer into the handler.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="state"></param>
|
|
||||||
public virtual void FeedPointerState(PointerState state) {
|
|
||||||
if (state.is2D) state.position2D = MapPointerToBrowser(state.position2D, state.id);
|
|
||||||
else {
|
|
||||||
Debug.DrawRay(state.position3D.origin, state.position3D.direction * (Mathf.Min(500, maxDistance)), Color.cyan);
|
|
||||||
state.position2D = MapRayToBrowser(state.position3D, state.id);
|
|
||||||
//Debug.Log("Pointer " + state.id + " at " + state.position3D.origin + " pointing " + state.position3D.direction + " maps to " + state.position2D);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (float.IsNaN(state.position2D.x)) return;
|
|
||||||
|
|
||||||
if (state.id == currentPointerId) {
|
|
||||||
p_currentOver = currentPointers.Count;
|
|
||||||
|
|
||||||
if (state.activeButtons != 0) p_currentDown = currentPointers.Count;
|
|
||||||
} else {
|
|
||||||
p_anyOver = currentPointers.Count;
|
|
||||||
|
|
||||||
if (state.activeButtons != 0) p_anyDown = currentPointers.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPointers.Add(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void CalculatePointer() {
|
|
||||||
// if (!appFocused) {
|
|
||||||
// MouseIsOff();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The position/priority we feed to the browser is determined in this order:
|
|
||||||
* - Pointer we used earlier with a button down
|
|
||||||
* - Pointer with a button down
|
|
||||||
* - Pointer we used earlier
|
|
||||||
* - Any pointer that is over the browser
|
|
||||||
* Pointers that aren't over the browser are ignored.
|
|
||||||
* If multiple pointers meet the criteria we may pick any that qualify.
|
|
||||||
*/
|
|
||||||
|
|
||||||
PointerState stateToUse;
|
|
||||||
if (p_currentDown >= 0) {
|
|
||||||
//last frame's pointer with a button down
|
|
||||||
stateToUse = currentPointers[p_currentDown];
|
|
||||||
} else if (p_anyDown >= 0) {
|
|
||||||
//mouse button count became > 0 this frame
|
|
||||||
stateToUse = currentPointers[p_anyDown];
|
|
||||||
} else if (p_currentOver >= 0) {
|
|
||||||
//just hovering (use the pointer from last frame)
|
|
||||||
stateToUse = currentPointers[p_currentOver];
|
|
||||||
} else if (p_anyOver >= 0) {
|
|
||||||
//just hovering (use any pointer over us)
|
|
||||||
stateToUse = currentPointers[p_anyOver];
|
|
||||||
} else {
|
|
||||||
//no pointers over us
|
|
||||||
MouseIsOff();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseIsOver();
|
|
||||||
|
|
||||||
if (MouseButtons == 0 && stateToUse.activeButtons != 0) {
|
|
||||||
//no buttons -> 1+ buttons
|
|
||||||
onClick();
|
|
||||||
//start drag prevention
|
|
||||||
dragging = false;
|
|
||||||
mouseDownPosition = stateToUse.position2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (float.IsNaN(stateToUse.position2D.x)) Debug.LogError("Using an invalid pointer");// "shouldn't happen"
|
|
||||||
|
|
||||||
if (stateToUse.activeButtons != 0 || MouseButtons != 0) {
|
|
||||||
//Button(s) held or being released, do some extra logic to prevent unintentional dragging during clicks.
|
|
||||||
|
|
||||||
if (!dragging && stateToUse.activeButtons != 0) {//only check distance if buttons(s) held and not already dragging
|
|
||||||
//Check to see if we passed the drag threshold.
|
|
||||||
var size = browser.Size;
|
|
||||||
var distance = Vector2.Distance(
|
|
||||||
Vector2.Scale(stateToUse.position2D, size),//convert from [0, 1] to pixels
|
|
||||||
Vector2.Scale(mouseDownPosition, size)//convert from [0, 1] to pixels
|
|
||||||
);
|
|
||||||
|
|
||||||
if (distance > dragMovementThreshold) {
|
|
||||||
dragging = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dragging) MousePosition = stateToUse.position2D;
|
|
||||||
else MousePosition = mouseDownPosition;
|
|
||||||
} else {
|
|
||||||
//no buttons held (or being release), no need to fiddle with the position
|
|
||||||
MousePosition = stateToUse.position2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseButtons = stateToUse.activeButtons;
|
|
||||||
MouseScroll = stateToUse.scrollDelta;
|
|
||||||
currentPointerId = stateToUse.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void OnGUI() {
|
|
||||||
keyEvents.Feed(Event.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool mouseWasOver = false;
|
|
||||||
protected void MouseIsOver() {
|
|
||||||
MouseHasFocus = true;
|
|
||||||
KeyboardHasFocus = true;
|
|
||||||
|
|
||||||
if (BrowserCursor != null) {
|
|
||||||
CursorUpdated();
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseWasOver = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void MouseIsOff() {
|
|
||||||
// if (BrowserCursor != null && mouseWasOver) {
|
|
||||||
// SetCursor(null);
|
|
||||||
// }
|
|
||||||
mouseWasOver = false;
|
|
||||||
|
|
||||||
MouseHasFocus = false;
|
|
||||||
if (focusForceCount <= 0) KeyboardHasFocus = false;
|
|
||||||
MouseButtons = 0;
|
|
||||||
MouseScroll = Vector2.zero;
|
|
||||||
|
|
||||||
currentPointerId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void CursorUpdated() {
|
|
||||||
// SetCursor(BrowserCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int focusForceCount = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a flag to keep the keyboard focus on this browser, even if it has no pointers.
|
|
||||||
/// Useful for focusing it to type things in via external keyboard.
|
|
||||||
///
|
|
||||||
/// Call again with force = false to return to the default behavior. (You must call force
|
|
||||||
/// on and force off an equal number of times to revert to the default behavior.)
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="force"></param>
|
|
||||||
public void ForceKeyboardHasFocus(bool force) {
|
|
||||||
if (force) ++focusForceCount;
|
|
||||||
else --focusForceCount;
|
|
||||||
if (focusForceCount == 1) KeyboardHasFocus = true;
|
|
||||||
else if (focusForceCount == 0) KeyboardHasFocus = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Input Handlers
|
|
||||||
|
|
||||||
[Tooltip("Camera to use to interpret 2D inputs and to originate FPS rays from. Defaults to Camera.main.")]
|
|
||||||
public Camera viewCamera;
|
|
||||||
|
|
||||||
public bool enableMouseInput = true;
|
|
||||||
public bool enableTouchInput = true;
|
|
||||||
public bool enableFPSInput = false;
|
|
||||||
public bool enableVRInput = false;
|
|
||||||
|
|
||||||
[Tooltip("(For ray-based interaction) How close must you be to the browser to be able to interact with it?")]
|
|
||||||
public float maxDistance = float.PositiveInfinity;
|
|
||||||
|
|
||||||
[Space(5)]
|
|
||||||
[Tooltip("Disable Input.simulateMouseWithTouches globally. This will prevent touches from appearing as mouse events.")]
|
|
||||||
public bool disableMouseEmulation = false;
|
|
||||||
|
|
||||||
protected virtual void OnHandlePointers() {
|
|
||||||
if (enableFPSInput) FeedFPS();
|
|
||||||
|
|
||||||
//special case to avoid duplicate pointer from the first touch (ignore mouse)
|
|
||||||
if (enableMouseInput && enableTouchInput && Input.simulateMouseWithTouches && Input.touchCount > 0) {
|
|
||||||
FeedTouchPointers();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableMouseInput) FeedMousePointer();
|
|
||||||
if (enableTouchInput) FeedTouchPointers();
|
|
||||||
#if UNITY_2017_2_OR_NEWER
|
|
||||||
if (enableVRInput) FeedVRPointers();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calls FeedPointerState with all the items in Input.touches.
|
|
||||||
/// (Does not happen automatically, call when desired.)
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void FeedTouchPointers() {
|
|
||||||
for (int i = 0; i < Input.touchCount; i++) {
|
|
||||||
var touch = Input.GetTouch(i);
|
|
||||||
|
|
||||||
FeedPointerState(new PointerState {
|
|
||||||
id = 10 + touch.fingerId,
|
|
||||||
is2D = true,
|
|
||||||
position2D = touch.position,
|
|
||||||
activeButtons = (touch.phase == TouchPhase.Began || touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary) ? MouseButton.Left : 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calls FeedPointerState with the current mouse state.
|
|
||||||
/// (Does not happen automatically, call when desired.)
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void FeedMousePointer() {
|
|
||||||
var buttons = (MouseButton)0;
|
|
||||||
if (Input.GetMouseButton(0)) buttons |= MouseButton.Left;
|
|
||||||
if (Input.GetMouseButton(1)) buttons |= MouseButton.Right;
|
|
||||||
if (Input.GetMouseButton(2)) buttons |= MouseButton.Middle;
|
|
||||||
|
|
||||||
FeedPointerState(new PointerState {
|
|
||||||
id = 1,
|
|
||||||
is2D = true,
|
|
||||||
position2D = Input.mousePosition,
|
|
||||||
activeButtons = buttons,
|
|
||||||
scrollDelta = Input.mouseScrollDelta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected virtual void FeedFPS() {
|
|
||||||
var buttons =
|
|
||||||
(Input.GetButton("Fire1") ? MouseButton.Left : 0) |
|
|
||||||
(Input.GetButton("Fire2") ? MouseButton.Right : 0) |
|
|
||||||
(Input.GetButton("Fire3") ? MouseButton.Middle : 0)
|
|
||||||
;
|
|
||||||
|
|
||||||
var camera = viewCamera ? viewCamera : Camera.main;
|
|
||||||
|
|
||||||
var scrollDelta = Input.mouseScrollDelta;
|
|
||||||
|
|
||||||
//Don't double-count scrolling if we are processing the mouse too.
|
|
||||||
if (enableMouseInput) scrollDelta = Vector2.zero;
|
|
||||||
|
|
||||||
FeedPointerState(new PointerState {
|
|
||||||
id = 2,
|
|
||||||
is2D = false,
|
|
||||||
position3D = new Ray(camera.transform.position, camera.transform.forward),
|
|
||||||
activeButtons = buttons,
|
|
||||||
scrollDelta = scrollDelta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#if UNITY_2017_2_OR_NEWER
|
|
||||||
protected VRBrowserHand[] vrHands = null;
|
|
||||||
protected virtual void FeedVRPointers() {
|
|
||||||
if (vrHands == null) {
|
|
||||||
vrHands = FindObjectsOfType<VRBrowserHand>();
|
|
||||||
if (vrHands.Length == 0 && XRSettings.enabled) {
|
|
||||||
Debug.LogWarning("VR input is enabled, but no VRBrowserHands were found in the scene", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < vrHands.Length; i++) {
|
|
||||||
if (!vrHands[i].Tracked) continue;
|
|
||||||
|
|
||||||
FeedPointerState(new PointerState {
|
|
||||||
id = 100 + i,
|
|
||||||
is2D = false,
|
|
||||||
position3D = new Ray(vrHands[i].transform.position, vrHands[i].transform.forward),
|
|
||||||
activeButtons = vrHands[i].DepressedButtons,
|
|
||||||
scrollDelta = vrHands[i].ScrollDelta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public virtual bool MouseHasFocus { get; protected set; }
|
|
||||||
public virtual Vector2 MousePosition { get; protected set; }
|
|
||||||
public virtual MouseButton MouseButtons { get; protected set; }
|
|
||||||
public virtual Vector2 MouseScroll { get; protected set; }
|
|
||||||
public virtual bool KeyboardHasFocus { get; protected set; }
|
|
||||||
public virtual List<Event> KeyEvents { get { return keyEvents.Events; } }
|
|
||||||
public virtual BrowserCursor BrowserCursor { get; protected set; }
|
|
||||||
public virtual BrowserInputSettings InputSettings { get; protected set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 7bd56d627d06bc64382a847aa240ae1d
|
|
||||||
timeCreated: 1495912815
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,126 +0,0 @@
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/** Attach this script to a GUI Image to use a browser on it. */
|
|
||||||
[RequireComponent(typeof(RawImage))]
|
|
||||||
public class PointerUIGUI :
|
|
||||||
PointerUIBase,
|
|
||||||
IBrowserUI,
|
|
||||||
ISelectHandler, IDeselectHandler,
|
|
||||||
IPointerEnterHandler, IPointerExitHandler,
|
|
||||||
IPointerDownHandler
|
|
||||||
{
|
|
||||||
protected RawImage myImage;
|
|
||||||
|
|
||||||
public bool enableInput = true;
|
|
||||||
public bool automaticResize = true;
|
|
||||||
|
|
||||||
public override void Awake() {
|
|
||||||
base.Awake();
|
|
||||||
myImage = GetComponent<RawImage>();
|
|
||||||
|
|
||||||
browser.afterResize += UpdateTexture;
|
|
||||||
// BrowserCursor.cursorChange += () => {
|
|
||||||
// SetCursor(BrowserCursor);
|
|
||||||
// };
|
|
||||||
|
|
||||||
rTransform = GetComponent<RectTransform>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnEnable() {
|
|
||||||
if (automaticResize) StartCoroutine(WatchResize());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Automatically resizes the browser to match the size of this object. */
|
|
||||||
private IEnumerator WatchResize() {
|
|
||||||
Rect currentSize = new Rect();
|
|
||||||
|
|
||||||
while (enabled) {
|
|
||||||
var rect = rTransform.rect;
|
|
||||||
|
|
||||||
if (rect.size.x <= 0 || rect.size.y <= 0) rect.size = new Vector2(512, 512);
|
|
||||||
if (rect.size != currentSize.size) {
|
|
||||||
browser.Resize((int)rect.size.x, (int)rect.size.y);
|
|
||||||
currentSize = rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void UpdateTexture(Texture2D texture) {
|
|
||||||
myImage.texture = texture;
|
|
||||||
myImage.uvRect = new Rect(0, 0, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BaseRaycaster raycaster;
|
|
||||||
protected RectTransform rTransform;
|
|
||||||
// protected List<RaycastResult> raycastResults = new List<RaycastResult>();
|
|
||||||
|
|
||||||
protected override Vector2 MapPointerToBrowser(Vector2 screenPosition, int pointerId) {
|
|
||||||
if (!raycaster) raycaster = GetComponentInParent<BaseRaycaster>();
|
|
||||||
|
|
||||||
Vector2 pos;
|
|
||||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
||||||
(RectTransform)transform, screenPosition, raycaster.eventCamera, out pos
|
|
||||||
);
|
|
||||||
pos.x = pos.x / rTransform.rect.width + rTransform.pivot.x;
|
|
||||||
pos.y = pos.y / rTransform.rect.height + rTransform.pivot.y;
|
|
||||||
return pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Vector2 MapRayToBrowser(Ray worldRay, int pointerId) {
|
|
||||||
var evs = EventSystem.current;
|
|
||||||
if (!evs) return new Vector2(float.NaN, float.NaN);
|
|
||||||
|
|
||||||
//todo: world-space GUI
|
|
||||||
return new Vector2(float.NaN, float.NaN);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GetCurrentHitLocation(out Vector3 pos, out Quaternion rot) {
|
|
||||||
//todo: world space GUI
|
|
||||||
pos = new Vector3(float.NaN, float.NaN, float.NaN);
|
|
||||||
rot = Quaternion.identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected bool _mouseHasFocus;
|
|
||||||
public override bool MouseHasFocus {
|
|
||||||
get { return _mouseHasFocus && enableInput; }
|
|
||||||
protected set { _mouseHasFocus = value; }
|
|
||||||
}
|
|
||||||
protected bool _keyboardHasFocus;
|
|
||||||
public override bool KeyboardHasFocus { get { return _keyboardHasFocus && enableInput; } }
|
|
||||||
|
|
||||||
public void OnSelect(BaseEventData eventData) {
|
|
||||||
_keyboardHasFocus = true;
|
|
||||||
Input.imeCompositionMode = IMECompositionMode.Off;//CEF will handle the IME
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDeselect(BaseEventData eventData) {
|
|
||||||
_keyboardHasFocus = false;
|
|
||||||
Input.imeCompositionMode = IMECompositionMode.Auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPointerEnter(PointerEventData eventData) {
|
|
||||||
_mouseHasFocus = true;
|
|
||||||
// SetCursor(BrowserCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnPointerExit(PointerEventData eventData) {
|
|
||||||
_mouseHasFocus = false;
|
|
||||||
// SetCursor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void OnPointerDown(PointerEventData eventData) {
|
|
||||||
EventSystem.current.SetSelectedGameObject(gameObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 9f0449828438f1c4eb0712205cc11bb7
|
|
||||||
timeCreated: 1495924470
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,71 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A BrowserUI that tracks pointer interaction through a camera to a mesh of some sort.
|
|
||||||
/// </summary>
|
|
||||||
[RequireComponent(typeof(MeshCollider))]
|
|
||||||
public class PointerUIMesh : PointerUIBase {
|
|
||||||
protected MeshCollider meshCollider;
|
|
||||||
|
|
||||||
protected Dictionary<int, RaycastHit> rayHits = new Dictionary<int, RaycastHit>();
|
|
||||||
|
|
||||||
[Tooltip("Which layers should UI rays collide with (and be able to hit)?")]
|
|
||||||
public LayerMask layerMask = -1;
|
|
||||||
|
|
||||||
public override void Awake() {
|
|
||||||
base.Awake();
|
|
||||||
meshCollider = GetComponent<MeshCollider>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Vector2 MapPointerToBrowser(Vector2 screenPosition, int pointerId) {
|
|
||||||
var camera = viewCamera ? viewCamera : Camera.main;
|
|
||||||
return MapRayToBrowser(camera.ScreenPointToRay(screenPosition), pointerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Vector2 MapRayToBrowser(Ray worldRay, int pointerId) {
|
|
||||||
RaycastHit hit;
|
|
||||||
var rayHit = Physics.Raycast(worldRay, out hit, maxDistance, layerMask);
|
|
||||||
|
|
||||||
//store hit data for GetCurrentHitLocation
|
|
||||||
rayHits[pointerId] = hit;
|
|
||||||
|
|
||||||
if (!rayHit || hit.collider.transform != meshCollider.transform) {
|
|
||||||
//not aimed at it
|
|
||||||
return new Vector3(float.NaN, float.NaN);
|
|
||||||
} else {
|
|
||||||
return hit.textureCoord;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void GetCurrentHitLocation(out Vector3 pos, out Quaternion rot) {
|
|
||||||
if (currentPointerId == 0) {
|
|
||||||
//no pointer
|
|
||||||
pos = new Vector3(float.NaN, float.NaN, float.NaN);
|
|
||||||
rot = Quaternion.identity;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hitInfo = rayHits[currentPointerId];
|
|
||||||
|
|
||||||
//We need to know which way is up, so the cursor has the correct "up".
|
|
||||||
//There's a couple ways to do this:
|
|
||||||
//1. Use the barycentric coordinates and some math to figure out what direction the collider's
|
|
||||||
// v (from the uv) is getting bigger/smaller, then do some math to find out what direction
|
|
||||||
// that is in world space.
|
|
||||||
//2. Just use the collider's local orientation's up. This isn't accurate on highly
|
|
||||||
// distorted meshes, but is much simpler to calculate.
|
|
||||||
//For now, we use method 2.
|
|
||||||
var up = hitInfo.collider.transform.up;
|
|
||||||
|
|
||||||
pos = hitInfo.point;
|
|
||||||
rot = Quaternion.LookRotation(-hitInfo.normal, up);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 70425c8c18e6a674da5c39ca0c09003c
|
|
||||||
timeCreated: 1495915500
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,100 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using UnityEngine;
|
|
||||||
using NativeCookie = ZenFulcrum.EmbeddedBrowser.BrowserNative.NativeCookie;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
public class Cookie {
|
|
||||||
|
|
||||||
public static void Init() {
|
|
||||||
//Empty function on this class to call so we can get the cctor to call on the correct thread.
|
|
||||||
//(Regex construction tends to crash if it tries to run from certain threads.)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private CookieManager cookies;
|
|
||||||
|
|
||||||
private NativeCookie original;
|
|
||||||
|
|
||||||
public string name = "", value = "", domain = "", path = "";
|
|
||||||
/** Creation/access time of the cookie. Mostly untested/unsupported at present. */
|
|
||||||
public DateTime creation, lastAccess;
|
|
||||||
/** Null for normal cookies, a time for cookies that expire. Mostly untested/unsupported at present. */
|
|
||||||
public DateTime? expires;
|
|
||||||
public bool secure, httpOnly;
|
|
||||||
|
|
||||||
public Cookie(CookieManager cookies) {
|
|
||||||
this.cookies = cookies;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Cookie(CookieManager cookies, NativeCookie cookie) {
|
|
||||||
this.cookies = cookies;
|
|
||||||
original = cookie;
|
|
||||||
Copy(original, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deletes this cookie from the browser. */
|
|
||||||
public void Delete() {
|
|
||||||
if (original == null) return;
|
|
||||||
|
|
||||||
BrowserNative.zfb_editCookie(cookies.browser.browserId, original, BrowserNative.CookieAction.Delete);
|
|
||||||
original = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Updates any changes to this cookie in the browser, creating the cookie if it's new. */
|
|
||||||
public void Update() {
|
|
||||||
if (original != null) Delete();
|
|
||||||
|
|
||||||
original = new NativeCookie();
|
|
||||||
Copy(this, original);
|
|
||||||
|
|
||||||
BrowserNative.zfb_editCookie(cookies.browser.browserId, original, BrowserNative.CookieAction.Create);
|
|
||||||
}
|
|
||||||
|
|
||||||
static readonly Regex dateRegex = new Regex(@"(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}).(\d{3})");
|
|
||||||
|
|
||||||
public static void Copy(NativeCookie src, Cookie dest) {
|
|
||||||
dest.name = src.name;
|
|
||||||
dest.value = src.value;
|
|
||||||
dest.domain = src.domain;
|
|
||||||
dest.path = src.path;
|
|
||||||
|
|
||||||
Func<string, DateTime> convert = s => {
|
|
||||||
var m = dateRegex.Match(s);
|
|
||||||
|
|
||||||
return new DateTime(
|
|
||||||
int.Parse(m.Groups[1].ToString()),
|
|
||||||
int.Parse(m.Groups[2].ToString()),
|
|
||||||
int.Parse(m.Groups[3].ToString()),
|
|
||||||
int.Parse(m.Groups[4].ToString()),
|
|
||||||
int.Parse(m.Groups[5].ToString()),
|
|
||||||
int.Parse(m.Groups[6].ToString()),
|
|
||||||
int.Parse(m.Groups[7].ToString())
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
dest.creation = convert(src.creation);
|
|
||||||
dest.expires = src.expires == null ? (DateTime?)null : convert(src.expires);
|
|
||||||
dest.lastAccess = convert(src.lastAccess);
|
|
||||||
|
|
||||||
dest.secure = src.secure != 0;
|
|
||||||
dest.httpOnly = src.httpOnly != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Copy(Cookie src, NativeCookie dest) {
|
|
||||||
dest.name = src.name;
|
|
||||||
dest.value = src.value;
|
|
||||||
dest.domain = src.domain;
|
|
||||||
dest.path = src.path;
|
|
||||||
|
|
||||||
Func<DateTime, string> convert = s => s.ToString("yyyy-MM-dd hh:mm:ss.fff");
|
|
||||||
|
|
||||||
dest.creation = convert(src.creation);
|
|
||||||
dest.expires = src.expires == null ? null : convert(src.expires.Value);
|
|
||||||
dest.lastAccess = convert(src.lastAccess);
|
|
||||||
|
|
||||||
dest.secure = src.secure ? (byte)1 : (byte)0;
|
|
||||||
dest.httpOnly = src.httpOnly ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: f7a2a645481f96e4682c86cc9b22dff9
|
|
||||||
timeCreated: 1478892646
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,94 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using AOT;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
public class CookieManager {
|
|
||||||
internal readonly Browser browser;
|
|
||||||
|
|
||||||
|
|
||||||
public CookieManager(Browser browser) {
|
|
||||||
this.browser = browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CookieFetch {
|
|
||||||
public BrowserNative.GetCookieFunc nativeCB;
|
|
||||||
public Promise<List<Cookie>> promise;
|
|
||||||
public CookieManager manager;
|
|
||||||
public List<Cookie> result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CookieFetch currentFetch;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of all cookies in the browser across all domains.
|
|
||||||
*
|
|
||||||
* Note that cookies are shared between browser instances.
|
|
||||||
*
|
|
||||||
* If the browser is not ready yet (browser.IsReady or WhenReady()) this will return an empty list.
|
|
||||||
*
|
|
||||||
* This method is not reentrant! You must wait for the returned promise to resolve before calling it again,
|
|
||||||
* even on a differnet object.
|
|
||||||
*/
|
|
||||||
public IPromise<List<Cookie>> GetCookies() {
|
|
||||||
if (currentFetch != null) {
|
|
||||||
//This method Wait for the previous promise to resolve, then make your call.
|
|
||||||
//If this limitation actually affects you, let me know.
|
|
||||||
throw new InvalidOperationException("GetCookies is not reentrant");
|
|
||||||
}
|
|
||||||
|
|
||||||
Cookie.Init();
|
|
||||||
|
|
||||||
var result = new List<Cookie>();
|
|
||||||
if (!browser.IsReady || !browser.enabled) return Promise<List<Cookie>>.Resolved(result);
|
|
||||||
var promise = new Promise<List<Cookie>>();
|
|
||||||
|
|
||||||
BrowserNative.GetCookieFunc cookieFunc = CB_GetCookieFunc;
|
|
||||||
BrowserNative.zfb_getCookies(browser.browserId, cookieFunc);
|
|
||||||
|
|
||||||
currentFetch = new CookieFetch {
|
|
||||||
promise = promise,
|
|
||||||
nativeCB = cookieFunc,
|
|
||||||
manager = this,
|
|
||||||
result = result,
|
|
||||||
};
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MonoPInvokeCallback(typeof(BrowserNative.GetCookieFunc))]
|
|
||||||
private static void CB_GetCookieFunc(BrowserNative.NativeCookie cookie) {
|
|
||||||
try {
|
|
||||||
if (cookie == null) {
|
|
||||||
var result = currentFetch.result;
|
|
||||||
var promise = currentFetch.promise;
|
|
||||||
currentFetch.manager.browser.RunOnMainThread(() => promise.Resolve(result));
|
|
||||||
currentFetch = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFetch.result.Add(new Cookie(currentFetch.manager, cookie));
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Debug.LogException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all cookies in the browser.
|
|
||||||
*/
|
|
||||||
public void ClearAll() {
|
|
||||||
if (browser.DeferUnready(ClearAll)) return;
|
|
||||||
|
|
||||||
BrowserNative.zfb_clearCookies(browser.browserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 536726be6d65c4f4fa53116569aa4be5
|
|
||||||
timeCreated: 1478910267
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,121 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for browser dialog boxes, like alert(). You don't need to use this directly, it will
|
|
||||||
* automatically be added where it's needed.
|
|
||||||
*/
|
|
||||||
[RequireComponent(typeof(Browser))]
|
|
||||||
public class DialogHandler : MonoBehaviour {
|
|
||||||
protected static string dialogPage;
|
|
||||||
|
|
||||||
public delegate void DialogCallback(bool affirm, string text1, string text2);
|
|
||||||
public delegate void MenuCallback(int commandId);
|
|
||||||
|
|
||||||
public static DialogHandler Create(Browser parent, DialogCallback dialogCallback, MenuCallback contextCallback) {
|
|
||||||
if (dialogPage == null) {
|
|
||||||
dialogPage = Resources.Load<TextAsset>("Browser/Dialogs").text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var go = new GameObject("Browser Dialog for " + parent.name);
|
|
||||||
var handler = go.AddComponent<DialogHandler>();
|
|
||||||
|
|
||||||
handler.parentBrowser = parent;
|
|
||||||
handler.dialogCallback = dialogCallback;
|
|
||||||
|
|
||||||
|
|
||||||
var db = handler.dialogBrowser = handler.GetComponent<Browser>();
|
|
||||||
|
|
||||||
db.UIHandler = parent.UIHandler;
|
|
||||||
db.EnableRendering = false;
|
|
||||||
db.EnableInput = false;
|
|
||||||
db.allowContextMenuOn = BrowserNative.ContextMenuOrigin.Editable;
|
|
||||||
//Use the parent texture. Except, we don't actually use it. So
|
|
||||||
//mostly we just mimic the size and don't consume more texture memory.
|
|
||||||
db.Resize(parent.Texture);
|
|
||||||
db.LoadHTML(dialogPage, "zfb://dialog");
|
|
||||||
db.UIHandler = parent.UIHandler;
|
|
||||||
|
|
||||||
db.RegisterFunction("reportDialogResult", args => {
|
|
||||||
dialogCallback(args[0], args[1], args[2]);
|
|
||||||
handler.Hide();
|
|
||||||
});
|
|
||||||
db.RegisterFunction("reportContextMenuResult", args => {
|
|
||||||
contextCallback(args[0]);
|
|
||||||
handler.Hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Browser parentBrowser;
|
|
||||||
protected Browser dialogBrowser;
|
|
||||||
protected DialogCallback dialogCallback;
|
|
||||||
protected MenuCallback contextCallback;
|
|
||||||
|
|
||||||
public void HandleDialog(BrowserNative.DialogType type, string text, string promptDefault = null) {
|
|
||||||
if (type == BrowserNative.DialogType.DLT_HIDE) {
|
|
||||||
Hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Show();
|
|
||||||
|
|
||||||
//Debug.Log("HandleDialog " + type + " text " + text + " prompt " + promptDefault);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case BrowserNative.DialogType.DLT_ALERT:
|
|
||||||
dialogBrowser.CallFunction("showAlert", text);
|
|
||||||
break;
|
|
||||||
case BrowserNative.DialogType.DLT_CONFIRM:
|
|
||||||
dialogBrowser.CallFunction("showConfirm", text);
|
|
||||||
break;
|
|
||||||
case BrowserNative.DialogType.DLT_PROMPT:
|
|
||||||
dialogBrowser.CallFunction("showPrompt", text, promptDefault);
|
|
||||||
break;
|
|
||||||
case BrowserNative.DialogType.DLT_PAGE_UNLOAD:
|
|
||||||
dialogBrowser.CallFunction("showConfirmNav", text);
|
|
||||||
break;
|
|
||||||
case BrowserNative.DialogType.DLT_PAGE_RELOAD:
|
|
||||||
dialogBrowser.CallFunction("showConfirmReload", text);
|
|
||||||
break;
|
|
||||||
case BrowserNative.DialogType.DLT_GET_AUTH:
|
|
||||||
dialogBrowser.CallFunction("showAuthPrompt", text);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException("type", type, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Show() {
|
|
||||||
parentBrowser.SetOverlay(dialogBrowser);
|
|
||||||
parentBrowser.EnableInput = false;
|
|
||||||
dialogBrowser.EnableInput = true;
|
|
||||||
dialogBrowser.UpdateCursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Hide() {
|
|
||||||
parentBrowser.SetOverlay(null);
|
|
||||||
parentBrowser.EnableInput = true;
|
|
||||||
dialogBrowser.EnableInput = false;
|
|
||||||
parentBrowser.UpdateCursor();
|
|
||||||
if (dialogBrowser.IsLoaded) dialogBrowser.CallFunction("reset");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HandleContextMenu(string menuJSON, int x, int y) {
|
|
||||||
if (menuJSON == null) {
|
|
||||||
Hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Show();
|
|
||||||
|
|
||||||
dialogBrowser.CallFunction("showContextMenu", menuJSON, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 8c5bf0b11246b1f42a262f8a64026d33
|
|
||||||
timeCreated: 1449001312
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,226 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for tracking and managing downloads.
|
|
||||||
* You can manage and handle downloads without this, but you may find it useful for dealing with the more
|
|
||||||
* common file downloading use cases.
|
|
||||||
*
|
|
||||||
* Usage: create one and call manager.ManageDownloads(browser) on a browser you want it to handle.
|
|
||||||
* or throw one in the scene and set "manageAllBrowsers" to true (before loading the scene) for it
|
|
||||||
* to automatically hook into all browsers that start in the scene.
|
|
||||||
*/
|
|
||||||
public class DownloadManager : MonoBehaviour {
|
|
||||||
|
|
||||||
[Tooltip("If true, this will find all the browser in the scene at startup and take control of their downloads.")]
|
|
||||||
public bool manageAllBrowsers = false;
|
|
||||||
|
|
||||||
[Tooltip("If true, a \"Save as\" style dialog will be given for all downloads.")]
|
|
||||||
public bool promptForFileNames;
|
|
||||||
|
|
||||||
[Tooltip("Where to save files. If null or blank, defaults to the user's downloads directory.")]
|
|
||||||
public string saveFolder;
|
|
||||||
|
|
||||||
[Tooltip("If given this text element will be updated with download status info.")]
|
|
||||||
public Text statusBar;
|
|
||||||
|
|
||||||
public class Download {
|
|
||||||
public Browser browser;
|
|
||||||
public int downloadId;
|
|
||||||
public string name;
|
|
||||||
public string path;
|
|
||||||
public int speed;
|
|
||||||
public int percent;
|
|
||||||
public string status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Download> downloads = new List<Download>();
|
|
||||||
|
|
||||||
public void Awake() {
|
|
||||||
if (manageAllBrowsers) {
|
|
||||||
foreach (var browser in FindObjectsOfType<Browser>()) {
|
|
||||||
ManageDownloads(browser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ManageDownloads(Browser browser) {
|
|
||||||
browser.onDownloadStarted = (id, info) => {
|
|
||||||
HandleDownloadStarted(browser, id, info);
|
|
||||||
};
|
|
||||||
browser.onDownloadStatus += (id, info) => {
|
|
||||||
HandleDownloadStatus(browser, id, info);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void HandleDownloadStarted(Browser browser, int downloadId, JSONNode info) {
|
|
||||||
//Debug.Log("Download requested: " + info.AsJSON);
|
|
||||||
|
|
||||||
var download = new Download {
|
|
||||||
browser = browser,
|
|
||||||
downloadId = downloadId,
|
|
||||||
name = info["suggestedName"],
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (promptForFileNames) {
|
|
||||||
browser.DownloadCommand(downloadId, BrowserNative.DownloadAction.Begin, null);
|
|
||||||
} else {
|
|
||||||
DirectoryInfo downloadFolder;
|
|
||||||
if (string.IsNullOrEmpty(saveFolder)) {
|
|
||||||
downloadFolder = new DirectoryInfo(GetUserDownloadFolder());
|
|
||||||
} else {
|
|
||||||
downloadFolder = new DirectoryInfo(saveFolder);
|
|
||||||
if (!downloadFolder.Exists) downloadFolder.Create();
|
|
||||||
}
|
|
||||||
|
|
||||||
var filePath = downloadFolder.FullName + "/" + new FileInfo(info["suggestedName"]).Name;
|
|
||||||
while (File.Exists(filePath)) {
|
|
||||||
var ext = Path.GetExtension(filePath);
|
|
||||||
var left = Path.GetFileNameWithoutExtension(filePath);
|
|
||||||
|
|
||||||
var time = DateTime.Now.ToString("yyyy-MM-dd hh_mm_ss");
|
|
||||||
filePath = downloadFolder.FullName + "/" + left + " " + time + ext;
|
|
||||||
}
|
|
||||||
browser.DownloadCommand(downloadId, BrowserNative.DownloadAction.Begin, filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
downloads.Add(download);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDownloadStatus(Browser browser, int downloadId, JSONNode info) {
|
|
||||||
//Debug.Log("Download status: " + info.AsJSON);
|
|
||||||
|
|
||||||
for (int i = 0; i < downloads.Count; i++) {
|
|
||||||
if (downloads[i].browser != browser || downloads[i].downloadId != downloadId) continue;
|
|
||||||
|
|
||||||
var download = downloads[i];
|
|
||||||
|
|
||||||
download.status = info["status"];
|
|
||||||
download.speed = info["speed"];
|
|
||||||
download.percent = info["percentComplete"];
|
|
||||||
if (!string.IsNullOrEmpty(info["fullPath"])) download.name = Path.GetFileName(info["fullPath"]);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update() {
|
|
||||||
if (statusBar) {
|
|
||||||
statusBar.text = Status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PauseAll() {
|
|
||||||
for (int i = 0; i < downloads.Count; i++) {
|
|
||||||
if (downloads[i].status == "working") {
|
|
||||||
downloads[i].browser.DownloadCommand(downloads[i].downloadId, BrowserNative.DownloadAction.Pause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void ResumeAll() {
|
|
||||||
for (int i = 0; i < downloads.Count; i++) {
|
|
||||||
if (downloads[i].status == "working") {
|
|
||||||
downloads[i].browser.DownloadCommand(downloads[i].downloadId, BrowserNative.DownloadAction.Resume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CancelAll() {
|
|
||||||
for (int i = 0; i < downloads.Count; i++) {
|
|
||||||
if (downloads[i].status == "working") {
|
|
||||||
downloads[i].browser.DownloadCommand(downloads[i].downloadId, BrowserNative.DownloadAction.Cancel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearAll() {
|
|
||||||
CancelAll();
|
|
||||||
downloads.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private StringBuilder sb = new StringBuilder();
|
|
||||||
/** Returns a string summarizing things that are downloading. */
|
|
||||||
public string Status {
|
|
||||||
get {
|
|
||||||
if (downloads.Count == 0) return "";
|
|
||||||
|
|
||||||
sb.Length = 0;
|
|
||||||
var rate = 0;
|
|
||||||
for (int i = downloads.Count - 1; i >= 0; i--) {
|
|
||||||
if (sb.Length > 0) sb.Append(", ");
|
|
||||||
sb.Append(downloads[i].name);
|
|
||||||
|
|
||||||
if (downloads[i].status == "working") {
|
|
||||||
if (downloads[i].percent >= 0) sb.Append(" (").Append(downloads[i].percent).Append("%)");
|
|
||||||
else sb.Append(" (??%)");
|
|
||||||
rate += downloads[i].speed;
|
|
||||||
} else {
|
|
||||||
sb.Append(" (").Append(downloads[i].status).Append(")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret = "Downloads";
|
|
||||||
if (rate > 0) {
|
|
||||||
ret += " (" + Mathf.Round(rate / (1024f * 1024) * 100) / 100f + "MiB/s)";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret + ": " + sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets the user's download folder, creating it if needed. */
|
|
||||||
public static string GetUserDownloadFolder() {
|
|
||||||
switch (System.Environment.OSVersion.Platform) {
|
|
||||||
case PlatformID.Win32NT: {
|
|
||||||
|
|
||||||
IntPtr path;
|
|
||||||
var r = SHGetKnownFolderPath(
|
|
||||||
new Guid("{374DE290-123F-4565-9164-39C4925E467B}"), //downloads
|
|
||||||
0x8000, //KF_FLAG_CREATE
|
|
||||||
IntPtr.Zero, //current user
|
|
||||||
out path
|
|
||||||
);
|
|
||||||
|
|
||||||
if (r == 0) {
|
|
||||||
var ret = Marshal.PtrToStringUni(path);
|
|
||||||
Marshal.FreeCoTaskMem(path);
|
|
||||||
return ret;
|
|
||||||
} else {
|
|
||||||
throw new Exception(
|
|
||||||
"Failed to get user download directory",
|
|
||||||
new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case PlatformID.Unix: {
|
|
||||||
var path = System.Environment.GetEnvironmentVariable("HOME") + "/Downloads";
|
|
||||||
var di = new DirectoryInfo(path);
|
|
||||||
if (!di.Exists) di.Create();
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
case PlatformID.MacOSX:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("Shell32.dll")]
|
|
||||||
private static extern int SHGetKnownFolderPath(
|
|
||||||
[MarshalAs(UnmanagedType.LPStruct)]Guid rfid, uint dwFlags, IntPtr hToken,
|
|
||||||
out IntPtr ppszPath
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 7d247427dfff6304db2b292811004b23
|
|
||||||
timeCreated: 1510867277
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,9 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 4c5a65551ba9c9949a3b3fefeb7fc1bd
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1447281187
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,133 +0,0 @@
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEditor;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
[CustomEditor(typeof(Browser))]
|
|
||||||
[CanEditMultipleObjects]
|
|
||||||
public class BrowserEditor : Editor {
|
|
||||||
|
|
||||||
private static string script = "document.body.style.background = 'red';\n";
|
|
||||||
private static string html = "Hello, <i>world</i>!\n";
|
|
||||||
|
|
||||||
private static string[] commandNames;
|
|
||||||
private static BrowserNative.FrameCommand[] commandValues;
|
|
||||||
|
|
||||||
|
|
||||||
static BrowserEditor() {
|
|
||||||
var els = Enum.GetValues(typeof(BrowserNative.FrameCommand));
|
|
||||||
commandNames = new string[els.Length];
|
|
||||||
commandValues = new BrowserNative.FrameCommand[els.Length];
|
|
||||||
int i = 0;
|
|
||||||
foreach (BrowserNative.FrameCommand cmd in els) {
|
|
||||||
commandNames[i] = cmd.ToString();
|
|
||||||
commandValues[i] = cmd;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool RequiresConstantRepaint() {
|
|
||||||
//The buttons get stale if we don't keep repainting them.
|
|
||||||
return Application.isPlaying;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnInspectorGUI() {
|
|
||||||
base.OnInspectorGUI();
|
|
||||||
|
|
||||||
if (Application.isPlaying && !serializedObject.isEditingMultipleObjects) {
|
|
||||||
RenderActions();
|
|
||||||
} else if (!Application.isPlaying) {
|
|
||||||
GUILayout.Label("Additional options available in play mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderActions() {
|
|
||||||
var browser = (Browser)target;
|
|
||||||
|
|
||||||
if (!browser.IsReady) {
|
|
||||||
GUILayout.Label("Starting...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginVertical("box");
|
|
||||||
GUILayout.Label("Apply items above:");
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal("box");
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("Go to URL")) browser.LoadURL(serializedObject.FindProperty("_url").stringValue, false);
|
|
||||||
if (GUILayout.Button("Force to URL")) browser.Url = serializedObject.FindProperty("_url").stringValue;
|
|
||||||
if (GUILayout.Button("Resize")) {
|
|
||||||
browser.Resize(
|
|
||||||
serializedObject.FindProperty("_width").intValue,
|
|
||||||
serializedObject.FindProperty("_height").intValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GUILayout.Button("Set Zoom")) browser.Zoom = serializedObject.FindProperty("_zoom").floatValue;
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.Label("Actions:");
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
|
||||||
{
|
|
||||||
GUI.enabled = browser.CanGoBack;
|
|
||||||
if (GUILayout.Button("Go back")) browser.GoBack();
|
|
||||||
GUI.enabled = browser.CanGoForward;
|
|
||||||
if (GUILayout.Button("Go forward")) browser.GoForward();
|
|
||||||
GUI.enabled = true;
|
|
||||||
|
|
||||||
|
|
||||||
if (browser.IsLoadingRaw) {
|
|
||||||
if (GUILayout.Button("Stop")) browser.Stop();
|
|
||||||
} else {
|
|
||||||
if (GUILayout.Button("Reload")) browser.Reload();
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("Force Reload")) browser.Reload(true);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
|
||||||
{
|
|
||||||
if (GUILayout.Button("Show Dev Tools")) browser.ShowDevTools();
|
|
||||||
if (GUILayout.Button("Hide Dev Tools")) browser.ShowDevTools(false);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
|
|
||||||
GUILayout.Label("Script:");
|
|
||||||
script = EditorGUILayout.TextArea(script);
|
|
||||||
GUILayout.BeginHorizontal();
|
|
||||||
if (GUILayout.Button("Eval JavaScript")) {
|
|
||||||
browser.EvalJS(script, "editor command");
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("Eval JavaScript CSP")) {
|
|
||||||
browser.EvalJSCSP(script, "editor command");
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
int pVal = EditorGUILayout.Popup("Send Command:", -1, commandNames);
|
|
||||||
if (pVal != -1) {
|
|
||||||
browser.SendFrameCommand(commandValues[pVal]);
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label("HTML:");
|
|
||||||
html = EditorGUILayout.TextArea(html);
|
|
||||||
if (GUILayout.Button("Load HTML")) {
|
|
||||||
browser.LoadHTML(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
GUILayout.EndVertical();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 28d4a442795171f4b92b03a99fcbdc6f
|
|
||||||
timeCreated: 1447280847
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,13 +0,0 @@
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
[CustomPropertyDrawer(typeof(FlagsFieldAttribute))]
|
|
||||||
public class FlagsEditor : PropertyDrawer {
|
|
||||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
|
|
||||||
property.intValue = EditorGUI.MaskField(position, label, property.intValue, property.enumNames);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 4846ded01fb9d6b489529816869b1d20
|
|
||||||
timeCreated: 1450462477
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,332 +0,0 @@
|
||||||
//This file is partially subject to Chromium's BSD license, read the class notes for more details.
|
|
||||||
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEditor;
|
|
||||||
using CursorType = ZenFulcrum.EmbeddedBrowser.BrowserNative.CursorType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility for generating the cursor icons.
|
|
||||||
*
|
|
||||||
* This isn't really here for general usage, but if you're willing to read the source and
|
|
||||||
* fiddle with things this may give you a head start from starting with nothing.
|
|
||||||
*
|
|
||||||
* The default icons are pulled from
|
|
||||||
* https://chromium.googlesource.com/chromium/src.git/+/master/ui/resources/default_100_percent/common/pointers/
|
|
||||||
* https://chromium.googlesource.com/chromium/src.git/+/master/ui/resources/ui_resources.grd
|
|
||||||
* and
|
|
||||||
* https://chromium.googlesource.com/chromium/src.git/+/master/ui/base/cursor/cursors_aura.cc
|
|
||||||
* This tool is used with a local directory, {IconGenerator.path}, filled with those icons.
|
|
||||||
*
|
|
||||||
* You also need to add a "loading.png" to the folder.
|
|
||||||
*
|
|
||||||
* To use this script, update the local path to your icons, define ZF_ICON_GENERATOR, and run it in
|
|
||||||
* from Assets->ZF Browser->Generate Icons.
|
|
||||||
*/
|
|
||||||
[ExecuteInEditMode]
|
|
||||||
public class IconGenerator {
|
|
||||||
private const string path = @"/my/path/to/chromium/ui-resources/default_100_percent/common/pointers";
|
|
||||||
private const string destAsset = "ZFBrowser/Resources/Browser/Cursors";
|
|
||||||
|
|
||||||
public static bool useBig = false;
|
|
||||||
|
|
||||||
#if ZF_ICON_GENERATOR
|
|
||||||
[MenuItem("Assets/ZF Browser/Generate Icons")]
|
|
||||||
#endif
|
|
||||||
public static void GenerateIcons() {
|
|
||||||
var icons = new SortedDictionary<string, Texture2D>();
|
|
||||||
|
|
||||||
var w = -1;
|
|
||||||
var h = -1;
|
|
||||||
|
|
||||||
foreach (var file in Directory.GetFiles(path)) {
|
|
||||||
if (useBig && !file.Contains("_big.png")) continue;
|
|
||||||
if (!useBig && file.Contains("_big.png")) continue;
|
|
||||||
|
|
||||||
var tex = new Texture2D(0, 0);
|
|
||||||
tex.LoadImage(File.ReadAllBytes(file));
|
|
||||||
|
|
||||||
if (w < 0) {
|
|
||||||
w = tex.width;
|
|
||||||
h = tex.height;
|
|
||||||
} else if (w != tex.width || h != tex.height) {
|
|
||||||
throw new Exception("Icons are not all the same size. This differs: " + file);
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = Path.GetFileNameWithoutExtension(file);
|
|
||||||
if (useBig) name = name.Substring(0, name.Length - 4);
|
|
||||||
icons[name] = tex;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Also add one for "cursor: none"
|
|
||||||
icons["_none_"] = null;
|
|
||||||
|
|
||||||
var res = new Texture2D(w * icons.Count, h, TextureFormat.ARGB32, false);
|
|
||||||
|
|
||||||
var descData = new StringBuilder();
|
|
||||||
var namesToPositions = new Dictionary<string, int>();
|
|
||||||
var i = 0;
|
|
||||||
foreach (var kvp in icons) {
|
|
||||||
if (kvp.Value == null) {
|
|
||||||
Fill(new Color(0, 0, 0, 0), res, i * w, 0, w, h);
|
|
||||||
} else {
|
|
||||||
Copy(kvp.Value, res, i * w, 0);
|
|
||||||
}
|
|
||||||
namesToPositions[kvp.Key] = i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var kvp in mapping) {
|
|
||||||
var pos = -1;
|
|
||||||
try {
|
|
||||||
if (kvp.Value.name != "_custom_") pos = namesToPositions[kvp.Value.name];
|
|
||||||
} catch (KeyNotFoundException) {
|
|
||||||
throw new KeyNotFoundException("No file found for " + kvp.Value.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (descData.Length != 0) descData.Append("\n");
|
|
||||||
|
|
||||||
var hotspot = kvp.Value.hotspot;
|
|
||||||
if (!useBig) {
|
|
||||||
hotspot.x = Mathf.Round(hotspot.x * .5f) - 3;
|
|
||||||
hotspot.y = Mathf.Round(kvp.Value.hotspot.y * .5f) - 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
descData
|
|
||||||
.Append(kvp.Key).Append(",")
|
|
||||||
.Append(pos).Append(",")
|
|
||||||
.Append(hotspot.x).Append(",")
|
|
||||||
.Append(hotspot.y)
|
|
||||||
;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var resName = Application.dataPath + "/" + destAsset;
|
|
||||||
File.WriteAllBytes(
|
|
||||||
resName + ".png",
|
|
||||||
res.EncodeToPNG()
|
|
||||||
);
|
|
||||||
File.WriteAllText(
|
|
||||||
resName + ".csv",
|
|
||||||
descData.ToString()
|
|
||||||
);
|
|
||||||
|
|
||||||
AssetDatabase.Refresh();
|
|
||||||
|
|
||||||
Debug.Log("Wrote icons files to " + resName + ".(png|csv) size: " + w + "x" + h);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Fill(Color color, Texture2D dest, int sx, int sy, int w, int h) {
|
|
||||||
for (int x = sx; x < w; ++x) {
|
|
||||||
for (int y = sy; y < h; ++y) {
|
|
||||||
dest.SetPixel(x, y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Copy(Texture2D src, Texture2D dest, int destX, int destY) {
|
|
||||||
//slow, but fine for a utility
|
|
||||||
for (int x = 0; x < src.width; ++x) {
|
|
||||||
for (int y = 0; y < src.height; ++y) {
|
|
||||||
dest.SetPixel(x + destX, y + destY, src.GetPixel(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct CursorInfo {
|
|
||||||
public CursorInfo(string name, Vector2 hotspot) {
|
|
||||||
this.name = name;
|
|
||||||
this.hotspot = hotspot;
|
|
||||||
}
|
|
||||||
public string name;
|
|
||||||
public Vector2 hotspot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<CursorType, CursorInfo> mapping = new Dictionary<CursorType, CursorInfo>() {
|
|
||||||
//Hotspots in for the default Chromium cursors can be found in ui/base/cursor/cursors_aura.cc, this is adapted
|
|
||||||
//from there.
|
|
||||||
//Note that we are always using the 2x (_big) icons.
|
|
||||||
{
|
|
||||||
//{19, 11}, {38, 22}} alias kCursorAlias IDR_AURA_CURSOR_ALIAS CT_ALIAS
|
|
||||||
CursorType.Alias,
|
|
||||||
new CursorInfo("alias", new Vector2(19, 11))
|
|
||||||
}, {
|
|
||||||
//{30, 30}, {60, 60}} cell kCursorCell IDR_AURA_CURSOR_CELL CT_CELL
|
|
||||||
CursorType.Cell,
|
|
||||||
new CursorInfo("cell", new Vector2(30, 30))
|
|
||||||
}, {
|
|
||||||
//{35, 29}, {70, 58}} sb_h_double_arrow kCursorColumnResize IDR_AURA_CURSOR_COL_RESIZE CT_COLUMNRESIZE
|
|
||||||
CursorType.ColumnResize,
|
|
||||||
new CursorInfo("sb_h_double_arrow", new Vector2(35, 29))
|
|
||||||
}, {
|
|
||||||
//{11, 11}, {22, 22}} context_menu kCursorContextMenu IDR_AURA_CURSOR_CONTEXT_MENU CT_CONTEXTMENU
|
|
||||||
CursorType.ContextMenu,
|
|
||||||
new CursorInfo("context_menu", new Vector2(11, 11))
|
|
||||||
}, {
|
|
||||||
//{10, 10}, {20, 20}} copy kCursorCopy IDR_AURA_CURSOR_COPY CT_COPY
|
|
||||||
CursorType.Copy,
|
|
||||||
new CursorInfo("copy", new Vector2(10, 10))
|
|
||||||
}, {
|
|
||||||
//{31, 30}, {62, 60}} crosshair kCursorCross IDR_AURA_CURSOR_CROSSHAIR CT_CROSS
|
|
||||||
CursorType.Cross,
|
|
||||||
new CursorInfo("crosshair", new Vector2(31, 30))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} custom kCursorCustom IDR_NONE CT_CUSTOM
|
|
||||||
CursorType.Custom,
|
|
||||||
new CursorInfo("_custom_", new Vector2(-1, -1))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorEastPanning IDR_NONE CT_EASTPANNING
|
|
||||||
CursorType.EastPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{35, 29}, {70, 58}} sb_h_double_arrow kCursorEastResize IDR_AURA_CURSOR_EAST_RESIZE CT_EASTRESIZE
|
|
||||||
CursorType.EastResize,
|
|
||||||
new CursorInfo("sb_h_double_arrow", new Vector2(35, 29))
|
|
||||||
}, {
|
|
||||||
//{35, 29}, {70, 58}} sb_h_double_arrow kCursorEastWestResize IDR_AURA_CURSOR_EAST_WEST_RESIZE CT_EASTWESTRESIZE
|
|
||||||
CursorType.EastWestResize,
|
|
||||||
new CursorInfo("sb_h_double_arrow", new Vector2(35, 29))
|
|
||||||
}, {
|
|
||||||
//{21, 11}, {42, 22}} fleur kCursorGrab IDR_AURA_CURSOR_GRAB CT_GRAB
|
|
||||||
CursorType.Grab,
|
|
||||||
new CursorInfo("fleur", new Vector2(21, 11))
|
|
||||||
}, {
|
|
||||||
//{20, 12}, {40, 24}} hand3 kCursorGrabbing IDR_AURA_CURSOR_GRABBING CT_GRABBING
|
|
||||||
CursorType.Grabbing,
|
|
||||||
new CursorInfo("hand3", new Vector2(20, 12))
|
|
||||||
}, {
|
|
||||||
//{25, 7}, {50, 14}} hand2 kCursorHand IDR_AURA_CURSOR_HAND CT_HAND
|
|
||||||
CursorType.Hand,
|
|
||||||
new CursorInfo("hand2", new Vector2(25, 7))
|
|
||||||
}, {
|
|
||||||
//{10, 11}, {20, 22}} help kCursorHelp IDR_AURA_CURSOR_HELP CT_HELP
|
|
||||||
CursorType.Help,
|
|
||||||
new CursorInfo("help", new Vector2(10, 11))
|
|
||||||
}, {
|
|
||||||
//{30, 32}, {60, 64}} xterm kCursorIBeam IDR_AURA_CURSOR_IBEAM CT_IBEAM
|
|
||||||
CursorType.IBeam,
|
|
||||||
new CursorInfo("xterm", new Vector2(30, 32))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorMiddlePanning IDR_NONE CT_MIDDLEPANNING
|
|
||||||
CursorType.MiddlePanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{32, 31}, {64, 62}} move kCursorMove IDR_AURA_CURSOR_MOVE CT_MOVE
|
|
||||||
CursorType.Move,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{10, 10}, {20, 20}} nodrop kCursorNoDrop IDR_AURA_CURSOR_NO_DROP CT_NODROP
|
|
||||||
CursorType.NoDrop,
|
|
||||||
new CursorInfo("nodrop", new Vector2(10, 10))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorNone IDR_NONE CT_NONE
|
|
||||||
CursorType.None,
|
|
||||||
new CursorInfo("_none_", new Vector2(0, 0))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorNorthEastPanning IDR_NONE CT_NORTHEASTPANNING
|
|
||||||
CursorType.NorthEastPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{31, 28}, {62, 56}} top_right_corner kCursorNorthEastResize IDR_AURA_CURSOR_NORTH_EAST_RESIZE CT_NORTHEASTRESIZE
|
|
||||||
CursorType.NorthEastResize,
|
|
||||||
new CursorInfo("top_right_corner", new Vector2(31, 28))
|
|
||||||
}, {
|
|
||||||
//{32, 30}, {64, 60}} top_right_corner kCursorNorthEastSouthWestResize IDR_AURA_CURSOR_NORTH_EAST_SOUTH_WEST_RESIZE CT_NORTHEASTSOUTHWESTRESIZE
|
|
||||||
CursorType.NorthEastSouthWestResize,
|
|
||||||
new CursorInfo("top_right_corner", new Vector2(32, 30))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorNorthPanning IDR_NONE CT_NORTHPANNING
|
|
||||||
CursorType.NorthPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{29, 32}, {58, 64}} sb_v_double_arrow kCursorNorthResize IDR_AURA_CURSOR_NORTH_RESIZE CT_NORTHRESIZE
|
|
||||||
CursorType.NorthResize,
|
|
||||||
new CursorInfo("sb_v_double_arrow", new Vector2(29, 32))
|
|
||||||
}, {
|
|
||||||
//{29, 32}, {58, 64}} sb_v_double_arrow kCursorNorthSouthResize IDR_AURA_CURSOR_NORTH_SOUTH_RESIZE CT_NORTHSOUTHRESIZE
|
|
||||||
CursorType.NorthSouthResize,
|
|
||||||
new CursorInfo("sb_v_double_arrow", new Vector2(29, 32))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorNorthWestPanning IDR_NONE CT_NORTHWESTPANNING
|
|
||||||
CursorType.NorthWestPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{28, 28}, {56, 56}} top_left_corner kCursorNorthWestResize IDR_AURA_CURSOR_NORTH_WEST_RESIZE CT_NORTHWESTRESIZE
|
|
||||||
CursorType.NorthWestResize,
|
|
||||||
new CursorInfo("top_left_corner", new Vector2(28, 28))
|
|
||||||
}, {
|
|
||||||
//{32, 31}, {64, 62}} top_left_corner kCursorNorthWestSouthEastResize IDR_AURA_CURSOR_NORTH_WEST_SOUTH_EAST_RESIZE CT_NORTHWESTSOUTHEASTRESIZE
|
|
||||||
CursorType.NorthWestSouthEastResize,
|
|
||||||
new CursorInfo("top_left_corner", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{10, 10}, {20, 20}} nodrop kCursorNotAllowed IDR_AURA_CURSOR_NO_DROP CT_NOTALLOWED
|
|
||||||
CursorType.NotAllowed,
|
|
||||||
new CursorInfo("nodrop", new Vector2(10, 10))
|
|
||||||
}, {
|
|
||||||
//{10, 10}, {20, 20}} left_ptr kCursorPointer IDR_AURA_CURSOR_PTR CT_POINTER
|
|
||||||
CursorType.Pointer,
|
|
||||||
new CursorInfo("left_ptr", new Vector2(10, 10))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorProgress IDR_NONE CT_PROGRESS
|
|
||||||
CursorType.Progress,
|
|
||||||
new CursorInfo("loading", new Vector2(32, 32))
|
|
||||||
}, {
|
|
||||||
//{29, 32}, {58, 64}} sb_v_double_arrow kCursorRowResize IDR_AURA_CURSOR_ROW_RESIZE CT_ROWRESIZE
|
|
||||||
CursorType.RowResize,
|
|
||||||
new CursorInfo("sb_v_double_arrow", new Vector2(29, 32))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorSouthEastPanning IDR_NONE CT_SOUTHEASTPANNING
|
|
||||||
CursorType.SouthEastPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{28, 28}, {56, 56}} top_left_corner kCursorSouthEastResize IDR_AURA_CURSOR_SOUTH_EAST_RESIZE CT_SOUTHEASTRESIZE
|
|
||||||
CursorType.SouthEastResize,
|
|
||||||
new CursorInfo("top_left_corner", new Vector2(28, 28))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorSouthPanning IDR_NONE CT_SOUTHPANNING
|
|
||||||
CursorType.SouthPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{29, 32}, {58, 64}} sb_v_double_arrow kCursorSouthResize IDR_AURA_CURSOR_SOUTH_RESIZE CT_SOUTHRESIZE
|
|
||||||
CursorType.SouthResize,
|
|
||||||
new CursorInfo("sb_v_double_arrow", new Vector2(29, 32))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorSouthWestPanning IDR_NONE CT_SOUTHWESTPANNING
|
|
||||||
CursorType.SouthWestPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{31, 28}, {62, 56}} top_right_corner kCursorSouthWestResize IDR_AURA_CURSOR_SOUTH_WEST_RESIZE CT_SOUTHWESTRESIZE
|
|
||||||
CursorType.SouthWestResize,
|
|
||||||
new CursorInfo("top_right_corner", new Vector2(31, 28))
|
|
||||||
}, {
|
|
||||||
//{32, 30}, {64, 60}} xterm_horiz kCursorVerticalText IDR_AURA_CURSOR_XTERM_HORIZ CT_VERTICALTEXT
|
|
||||||
CursorType.VerticalText,
|
|
||||||
new CursorInfo("xterm_horiz", new Vector2(32, 30))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorWait IDR_NONE CT_WAIT
|
|
||||||
CursorType.Wait,
|
|
||||||
new CursorInfo("loading", new Vector2(32, 32))
|
|
||||||
}, {
|
|
||||||
//{??, ??}, {??, ??}} _unknown_ kCursorWestPanning IDR_NONE CT_WESTPANNING
|
|
||||||
CursorType.WestPanning,
|
|
||||||
new CursorInfo("move", new Vector2(32, 31))
|
|
||||||
}, {
|
|
||||||
//{35, 29}, {70, 58}} sb_h_double_arrow kCursorWestResize IDR_AURA_CURSOR_WEST_RESIZE CT_WESTRESIZE
|
|
||||||
CursorType.WestResize,
|
|
||||||
new CursorInfo("sb_h_double_arrow", new Vector2(35, 29))
|
|
||||||
}, {
|
|
||||||
//{25, 26}, {50, 52}} zoom_in kCursorZoomIn IDR_AURA_CURSOR_ZOOM_IN CT_ZOOMIN
|
|
||||||
CursorType.ZoomIn,
|
|
||||||
new CursorInfo("zoom_in", new Vector2(25, 26))
|
|
||||||
}, {
|
|
||||||
//{26, 26}, {52, 52}} zoom_out kCursorZoomOut IDR_AURA_CURSOR_ZOOM_OUT CT_ZOOMOUT
|
|
||||||
CursorType.ZoomOut,
|
|
||||||
new CursorInfo("zoom_out", new Vector2(26, 26))
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 7924e40fd70097143810a7ad82727b47
|
|
||||||
timeCreated: 1447967713
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,200 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEditor.Callbacks;
|
|
||||||
using UnityEngine;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getting CEF running on a build result requires some fiddling to get all the files in the right place.
|
|
||||||
*/
|
|
||||||
class PostBuildStandalone {
|
|
||||||
|
|
||||||
static readonly List<string> byBinFiles = new List<string>() {
|
|
||||||
"natives_blob.bin",
|
|
||||||
"snapshot_blob.bin",
|
|
||||||
"v8_context_snapshot.bin",
|
|
||||||
"icudtl.dat",
|
|
||||||
};
|
|
||||||
|
|
||||||
[PostProcessBuild(10)]
|
|
||||||
public static void PostprocessLinuxOrWindowsBuild(BuildTarget target, string buildFile) {
|
|
||||||
//prereq
|
|
||||||
var windows = target == BuildTarget.StandaloneWindows || target == BuildTarget.StandaloneWindows64;
|
|
||||||
var linux = target == BuildTarget.StandaloneLinux || target == BuildTarget.StandaloneLinuxUniversal || target == BuildTarget.StandaloneLinux64;
|
|
||||||
|
|
||||||
if (!windows && !linux) return;
|
|
||||||
|
|
||||||
if (target == BuildTarget.StandaloneLinux || target == BuildTarget.StandaloneLinuxUniversal) {
|
|
||||||
throw new Exception("ZFBrowser: Only x86_64 Linux is supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//base info
|
|
||||||
string buildType;
|
|
||||||
if (windows) buildType = "w" + (target == BuildTarget.StandaloneWindows64 ? "64" : "32");
|
|
||||||
else buildType = "l64";
|
|
||||||
|
|
||||||
Debug.Log("ZFBrowser: Post processing " + buildFile + " as " + buildType);
|
|
||||||
|
|
||||||
string buildName;
|
|
||||||
if (windows) buildName = Regex.Match(buildFile, @"/([^/]+)\.exe$").Groups[1].Value;
|
|
||||||
else buildName = Regex.Match(buildFile, @"\/([^\/]+?)(\.x86(_64)?)?$").Groups[1].Value;
|
|
||||||
|
|
||||||
var buildPath = Directory.GetParent(buildFile);
|
|
||||||
var dataPath = buildPath + "/" + buildName + "_Data";
|
|
||||||
var pluginsPath = dataPath + "/Plugins/";
|
|
||||||
|
|
||||||
|
|
||||||
//start copying
|
|
||||||
|
|
||||||
|
|
||||||
//can't use FileLocations because we may not be building the same type as the editor
|
|
||||||
var platformPluginsSrc = ZFFolder + "/Plugins/" + buildType;
|
|
||||||
|
|
||||||
//(Unity will copy the .dll and .so files for us)
|
|
||||||
|
|
||||||
//Copy "root" .bin files
|
|
||||||
foreach (var file in byBinFiles) {
|
|
||||||
File.Copy(platformPluginsSrc + "/" + file, pluginsPath + file, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
File.Copy(ZFFolder + "/ThirdPartyNotices.txt", pluginsPath + "/ThirdPartyNotices.txt", true);
|
|
||||||
|
|
||||||
//Copy the needed resources
|
|
||||||
var resSrcDir = platformPluginsSrc + "/CEFResources";
|
|
||||||
foreach (var filePath in Directory.GetFiles(resSrcDir)) {
|
|
||||||
var fileName = new FileInfo(filePath).Name;
|
|
||||||
if (fileName.EndsWith(".meta")) continue;
|
|
||||||
|
|
||||||
File.Copy(filePath, pluginsPath + fileName, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Slave process (doesn't get automatically copied by Unity like the shared libs)
|
|
||||||
var exeExt = windows ? ".exe" : "";
|
|
||||||
File.Copy(
|
|
||||||
platformPluginsSrc + "/" + FileLocations.SlaveExecutable + exeExt,
|
|
||||||
pluginsPath + FileLocations.SlaveExecutable + exeExt,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
if (linux) MakeExecutable(pluginsPath + FileLocations.SlaveExecutable + exeExt);
|
|
||||||
|
|
||||||
//Locales
|
|
||||||
var localesSrcDir = platformPluginsSrc + "/CEFResources/locales";
|
|
||||||
var localesDestDir = dataPath + "/Plugins/locales";
|
|
||||||
Directory.CreateDirectory(localesDestDir);
|
|
||||||
foreach (var filePath in Directory.GetFiles(localesSrcDir)) {
|
|
||||||
var fileName = new FileInfo(filePath).Name;
|
|
||||||
if (fileName.EndsWith(".meta")) continue;
|
|
||||||
File.Copy(filePath, localesDestDir + "/" + fileName, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Newer versions of Unity put the shared libs in the wrong place. Move them to where we expect them.
|
|
||||||
if (linux && File.Exists(pluginsPath + "x86_64/zf_cef.so")) {
|
|
||||||
foreach (var libFile in new[] {"zf_cef.so", "libEGL.so", "libGLESv2.so", "libwidevinecdmadapter.so", "libZFProxyWeb.so"}) {
|
|
||||||
ForceMove(pluginsPath + "x86_64/" + libFile, pluginsPath + libFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteBrowserAssets(dataPath + "/" + StandaloneWebResources.DefaultPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
[PostProcessBuild(10)]
|
|
||||||
public static void PostprocessMacBuild(BuildTarget target, string buildFile) {
|
|
||||||
#if UNITY_2017_3_OR_NEWER
|
|
||||||
if (target != BuildTarget.StandaloneOSX) return;
|
|
||||||
#else
|
|
||||||
if (target == BuildTarget.StandaloneOSXUniversal || target == BuildTarget.StandaloneOSXIntel) {
|
|
||||||
throw new Exception("Only x86_64 is supported");
|
|
||||||
}
|
|
||||||
if (target != BuildTarget.StandaloneOSXIntel64) return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Debug.Log("Post processing " + buildFile);
|
|
||||||
|
|
||||||
//var buildName = Regex.Match(buildFile, @"\/([^\/]+?)\.app$").Groups[1].Value;
|
|
||||||
var buildPath = buildFile;
|
|
||||||
var platformPluginsSrc = ZFFolder + "/Plugins/m64";
|
|
||||||
|
|
||||||
//Copy app bits
|
|
||||||
CopyDirectory(
|
|
||||||
platformPluginsSrc + "/BrowserLib.app/Contents/Frameworks/Chromium Embedded Framework.framework",
|
|
||||||
buildPath + "/Contents/Frameworks/Chromium Embedded Framework.framework"
|
|
||||||
);
|
|
||||||
CopyDirectory(
|
|
||||||
platformPluginsSrc + "/BrowserLib.app/Contents/Frameworks/ZFGameBrowser.app",
|
|
||||||
buildPath + "/Contents/Frameworks/ZFGameBrowser.app"
|
|
||||||
);
|
|
||||||
|
|
||||||
MakeExecutable(buildPath + "/BrowserLib.app/Contents/Frameworks/ZFGameBrowser.app/Contents/MacOS/ZFGameBrowser");
|
|
||||||
|
|
||||||
if (!Directory.Exists(buildPath + "/Contents/Plugins")) Directory.CreateDirectory(buildPath + "/Contents/Plugins");
|
|
||||||
File.Copy(platformPluginsSrc + "/libZFProxyWeb.dylib", buildPath + "/Contents/Plugins/libZFProxyWeb.dylib", true);
|
|
||||||
|
|
||||||
File.Copy(ZFFolder + "/ThirdPartyNotices.txt", buildPath + "/ThirdPartyNotices.txt", true);
|
|
||||||
|
|
||||||
//BrowserAssets
|
|
||||||
WriteBrowserAssets(buildPath + "/Contents/" + StandaloneWebResources.DefaultPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void WriteBrowserAssets(string path) {
|
|
||||||
//Debug.Log("Writing browser assets to " + path);
|
|
||||||
|
|
||||||
var htmlDir = Application.dataPath + "/../BrowserAssets";
|
|
||||||
var allData = new Dictionary<string, byte[]>();
|
|
||||||
if (Directory.Exists(htmlDir)) {
|
|
||||||
foreach (var file in Directory.GetFiles(htmlDir, "*", SearchOption.AllDirectories)) {
|
|
||||||
var localPath = file.Substring(htmlDir.Length).Replace("\\", "/");
|
|
||||||
allData[localPath] = File.ReadAllBytes(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wr = new StandaloneWebResources(path);
|
|
||||||
wr.WriteData(allData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ForceMove(string src, string dest) {
|
|
||||||
if (File.Exists(dest)) File.Delete(dest);
|
|
||||||
File.Move(src, dest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ZFFolder {
|
|
||||||
get {
|
|
||||||
var path = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileName();
|
|
||||||
path = Directory.GetParent(path).Parent.Parent.FullName;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CopyDirectory(string src, string dest) {
|
|
||||||
foreach (var dir in Directory.GetDirectories(src, "*", SearchOption.AllDirectories)) {
|
|
||||||
Directory.CreateDirectory(dir.Replace(src, dest));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var file in Directory.GetFiles(src, "*", SearchOption.AllDirectories)) {
|
|
||||||
if (file.EndsWith(".meta")) continue;
|
|
||||||
File.Copy(file, file.Replace(src, dest), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void MakeExecutable(string fileName) {
|
|
||||||
#if UNITY_EDITOR_WIN
|
|
||||||
Debug.LogWarning("ZFBrowser: Be sure to mark the file \"" + fileName + "\" as executable (chmod +x) when you distribute it. If it's not executable the browser won't work.");
|
|
||||||
#else
|
|
||||||
//dec 493 = oct 755 = -rwxr-xr-x
|
|
||||||
chmod(fileName, 493);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[DllImport("__Internal")] static extern int symlink(string destStr, string symFile);
|
|
||||||
[DllImport("__Internal")] static extern int chmod(string file, int mode);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 821c048fda6531349be543e9b923a328
|
|
||||||
timeCreated: 1449181504
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"name": "ZFBrowser-Editor",
|
|
||||||
"references": [
|
|
||||||
"ZFBrowser"
|
|
||||||
],
|
|
||||||
"includePlatforms": [
|
|
||||||
"Editor"
|
|
||||||
],
|
|
||||||
"excludePlatforms": []
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: fe934ebeb18e2174a8b814db25fbce70
|
|
||||||
timeCreated: 1518479097
|
|
||||||
licenseType: Store
|
|
||||||
AssemblyDefinitionImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,57 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using UnityEngine;
|
|
||||||
#if UNITY_2017_3_OR_NEWER
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebResources implementation that grabs resources directly from Assets/../BrowserAssets.
|
|
||||||
*/
|
|
||||||
class EditorWebResources : WebResources {
|
|
||||||
protected string basePath;
|
|
||||||
|
|
||||||
public EditorWebResources() {
|
|
||||||
//NB: If you try to read Application.dataPath later you may not be on the main thread and it won't work.
|
|
||||||
basePath = Path.GetDirectoryName(Application.dataPath) + "/BrowserAssets";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks for "../asdf", "asdf/..\asdf", etc.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Regex matchDots = new Regex(@"(^|[/\\])\.[2,]($|[/\\])");
|
|
||||||
|
|
||||||
public override void HandleRequest(int id, string url) {
|
|
||||||
var parsedURL = new Uri(url);
|
|
||||||
|
|
||||||
#if UNITY_2017_3_OR_NEWER
|
|
||||||
var path = UnityWebRequest.UnEscapeURL(parsedURL.AbsolutePath);
|
|
||||||
#else
|
|
||||||
var path = WWW.UnEscapeURL(parsedURL.AbsolutePath);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (matchDots.IsMatch(path)) {
|
|
||||||
SendError(id, "Invalid path", 400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = new FileInfo(Application.dataPath + "/../BrowserAssets/" + path);
|
|
||||||
|
|
||||||
if (!file.Exists) {
|
|
||||||
SendError(id, "Not found", 404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//fixme: send 404 if file capitalization doesn't match. (Unfortunately, not a quick one-liner)
|
|
||||||
|
|
||||||
SendFile(id, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 59d86905d3bcefc4586e70747332bb6f
|
|
||||||
timeCreated: 1449617902
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,238 +0,0 @@
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
#define ZF_OSX
|
|
||||||
#endif
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper/worker class for displaying an external keyboard and
|
|
||||||
/// sending the input to a browser.
|
|
||||||
///
|
|
||||||
/// Don't put this script on your target browser directly, add a separate browser
|
|
||||||
/// to the scene and attach it to that instead.
|
|
||||||
///
|
|
||||||
/// </summary>
|
|
||||||
[RequireComponent(typeof(Browser))]
|
|
||||||
[RequireComponent(typeof(PointerUIBase))]
|
|
||||||
public class ExternalKeyboard : MonoBehaviour {
|
|
||||||
|
|
||||||
[Tooltip("Set to true before startup to have the keyboard automatically hook to the browser with the most recently focused text field.")]
|
|
||||||
public bool automaticFocus;
|
|
||||||
|
|
||||||
[Tooltip("Browser to start as the focused browser for this keyboard. Not really needed if automaticFocus is on.")]
|
|
||||||
public Browser initialBrowser;
|
|
||||||
|
|
||||||
[Tooltip("Set to true to have the keyboard automatically hide when we don't have a text entry box to type into.")]
|
|
||||||
public bool hideWhenUnneeded = true;
|
|
||||||
|
|
||||||
protected PointerUIBase activeBrowserUI;
|
|
||||||
protected Browser keyboardBrowser;
|
|
||||||
protected bool forcingFocus;
|
|
||||||
|
|
||||||
protected Browser _activeBrowser;
|
|
||||||
/// <summary>
|
|
||||||
/// Browser that gets input if we press keys on the keyboard.
|
|
||||||
/// </summary>
|
|
||||||
protected virtual Browser ActiveBrowser {
|
|
||||||
get { return _activeBrowser; }
|
|
||||||
set {
|
|
||||||
_SetActiveBrowser(value);
|
|
||||||
DoFocus(_activeBrowser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void _SetActiveBrowser(Browser browser) {
|
|
||||||
if (ActiveBrowser) {
|
|
||||||
if (activeBrowserUI && forcingFocus) {
|
|
||||||
activeBrowserUI.ForceKeyboardHasFocus(false);
|
|
||||||
forcingFocus = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_activeBrowser = browser;
|
|
||||||
activeBrowserUI = ActiveBrowser.GetComponent<PointerUIBase>();
|
|
||||||
if (!activeBrowserUI) {
|
|
||||||
//We can't focus the browser when we type, so the typed letters won't appear as we type.
|
|
||||||
Debug.LogWarning("Browser does not haver a PointerUI, external keyboard may not work properly.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the focus of the keyboard changes.
|
|
||||||
/// The browser is the browser we are focused on (may or may not be different), editable will be true if we
|
|
||||||
/// are expected to type in the new focus, false if not.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<Browser, bool> onFocusChange = (browser, editable) => {};
|
|
||||||
|
|
||||||
public void Awake() {
|
|
||||||
var keyboardPage = Resources.Load<TextAsset>("Browser/Keyboard").text;
|
|
||||||
|
|
||||||
keyboardBrowser = GetComponent<Browser>();
|
|
||||||
|
|
||||||
keyboardBrowser.onBrowserFocus += OnBrowserFocus;
|
|
||||||
keyboardBrowser.LoadHTML(keyboardPage);
|
|
||||||
keyboardBrowser.RegisterFunction("textTyped", TextTyped);
|
|
||||||
keyboardBrowser.RegisterFunction("commandEntered", CommandEntered);
|
|
||||||
|
|
||||||
if (initialBrowser) ActiveBrowser = initialBrowser;
|
|
||||||
|
|
||||||
if (automaticFocus) {
|
|
||||||
StartCoroutine(FindAndListenForBrowsers());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IEnumerator FindAndListenForBrowsers() {
|
|
||||||
yield return null;
|
|
||||||
foreach (var browser in FindObjectsOfType<Browser>()) {
|
|
||||||
if (browser == keyboardBrowser) continue;
|
|
||||||
ObserveBrowser(browser);
|
|
||||||
}
|
|
||||||
Browser.onAnyBrowserCreated += ObserveBrowser;
|
|
||||||
//in theory we shouldn't need to deal with browsers being destroyed since the whole callback chain should get cleaned up
|
|
||||||
//(might need some more work if you repeatedly destroy and recreate keyboards, though)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void ObserveBrowser(Browser browser) {
|
|
||||||
browser.onNodeFocus += (tagName, editable, value) => {
|
|
||||||
if (!this) return;
|
|
||||||
if (!browser.focusState.hasMouseFocus) return;
|
|
||||||
|
|
||||||
DoFocus(browser);
|
|
||||||
};
|
|
||||||
|
|
||||||
var pointerUI = browser.GetComponent<PointerUIBase>();
|
|
||||||
if (pointerUI) {
|
|
||||||
pointerUI.onClick += () => {
|
|
||||||
DoFocus(browser);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void DoFocus(Browser browser) {
|
|
||||||
if (browser != ActiveBrowser) {
|
|
||||||
_SetActiveBrowser(browser);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool visible;
|
|
||||||
if (browser) visible = browser.focusState.focusedNodeEditable;
|
|
||||||
else visible = false;
|
|
||||||
SetVisible(visible);
|
|
||||||
|
|
||||||
onFocusChange(_activeBrowser, visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void SetVisible(bool visible) {
|
|
||||||
var renderer = GetComponent<Renderer>();
|
|
||||||
if (renderer) renderer.enabled = visible;
|
|
||||||
var collider = GetComponent<Collider>();
|
|
||||||
if (collider) collider.enabled = visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnBrowserFocus(bool mouseFocused, bool kbFocused) {
|
|
||||||
//when our keyboard is focused, focus the browser we will be typing into.
|
|
||||||
if (!activeBrowserUI) return;
|
|
||||||
|
|
||||||
if ((mouseFocused || kbFocused) && !forcingFocus) {
|
|
||||||
activeBrowserUI.ForceKeyboardHasFocus(true);
|
|
||||||
forcingFocus = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(mouseFocused || kbFocused) && forcingFocus) {
|
|
||||||
activeBrowserUI.ForceKeyboardHasFocus(false);
|
|
||||||
forcingFocus = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void CommandEntered(JSONNode args) {
|
|
||||||
if (!ActiveBrowser) return;
|
|
||||||
|
|
||||||
string command = args[0];
|
|
||||||
bool shiftPressed = args[1];
|
|
||||||
|
|
||||||
if (shiftPressed) ActiveBrowser.PressKey(KeyCode.LeftShift, KeyAction.Press);
|
|
||||||
|
|
||||||
|
|
||||||
#if ZF_OSX
|
|
||||||
const KeyCode wordShifter = KeyCode.LeftAlt;
|
|
||||||
#else
|
|
||||||
const KeyCode wordShifter = KeyCode.LeftControl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
switch (command) {
|
|
||||||
case "backspace":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.Backspace);
|
|
||||||
break;
|
|
||||||
case "copy":
|
|
||||||
ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Copy);
|
|
||||||
break;
|
|
||||||
case "cut":
|
|
||||||
ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Cut);
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.Delete);
|
|
||||||
break;
|
|
||||||
case "down":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.DownArrow);
|
|
||||||
break;
|
|
||||||
case "end":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.End);
|
|
||||||
break;
|
|
||||||
case "home":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.Home);
|
|
||||||
break;
|
|
||||||
case "insert":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.Insert);
|
|
||||||
break;
|
|
||||||
case "left":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.LeftArrow);
|
|
||||||
break;
|
|
||||||
case "pageDown":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.PageDown);
|
|
||||||
break;
|
|
||||||
case "pageUp":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.PageUp);
|
|
||||||
break;
|
|
||||||
case "paste":
|
|
||||||
ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Paste);
|
|
||||||
break;
|
|
||||||
case "redo":
|
|
||||||
ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Redo);
|
|
||||||
break;
|
|
||||||
case "right":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.RightArrow);
|
|
||||||
break;
|
|
||||||
case "selectAll":
|
|
||||||
ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.SelectAll);
|
|
||||||
break;
|
|
||||||
case "undo":
|
|
||||||
ActiveBrowser.SendFrameCommand(BrowserNative.FrameCommand.Undo);
|
|
||||||
break;
|
|
||||||
case "up":
|
|
||||||
ActiveBrowser.PressKey(KeyCode.UpArrow);
|
|
||||||
break;
|
|
||||||
case "wordLeft":
|
|
||||||
ActiveBrowser.PressKey(wordShifter, KeyAction.Press);
|
|
||||||
ActiveBrowser.PressKey(KeyCode.LeftArrow);
|
|
||||||
ActiveBrowser.PressKey(wordShifter, KeyAction.Release);
|
|
||||||
break;
|
|
||||||
case "wordRight":
|
|
||||||
ActiveBrowser.PressKey(wordShifter, KeyAction.Press);
|
|
||||||
ActiveBrowser.PressKey(KeyCode.RightArrow);
|
|
||||||
ActiveBrowser.PressKey(wordShifter, KeyAction.Release);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shiftPressed) ActiveBrowser.PressKey(KeyCode.LeftShift, KeyAction.Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void TextTyped(JSONNode args) {
|
|
||||||
if (!ActiveBrowser) return;
|
|
||||||
|
|
||||||
ActiveBrowser.TypeText(args[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: faa68da6a73a843439bee4b722ce18b4
|
|
||||||
timeCreated: 1511320118
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,144 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using UnityEngine;
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
using UnityEditor;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
public static class FileLocations {
|
|
||||||
public const string SlaveExecutable = "ZFGameBrowser";
|
|
||||||
|
|
||||||
private static CEFDirs _dirs;
|
|
||||||
public static CEFDirs Dirs {
|
|
||||||
get { return _dirs ?? (_dirs = GetCEFDirs()); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CEFDirs {
|
|
||||||
/** Where to find cef.pak, et al */
|
|
||||||
public string resourcesPath;
|
|
||||||
/** Where to find .dll, .so, natives_blob.bin, etc */
|
|
||||||
public string binariesPath;
|
|
||||||
/** Where to find en-US.pak et al */
|
|
||||||
public string localesPath;
|
|
||||||
/** The executable to run for browser processes. */
|
|
||||||
public string subprocessFile;
|
|
||||||
/** Editor/application log file */
|
|
||||||
public string logFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CEFDirs GetCEFDirs() {
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
//In the editor we don't know exactly where we are at, but we can look up one of our scripts and move from there
|
|
||||||
var guids = AssetDatabase.FindAssets("EditorWebResources");
|
|
||||||
if (guids.Length != 1) throw new FileNotFoundException("Failed to locate a single EditorWebResources file");
|
|
||||||
string scriptPath = AssetDatabase.GUIDToAssetPath(guids[0]);
|
|
||||||
|
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
|
||||||
var baseDir = Directory.GetParent(scriptPath).Parent.FullName + "/Plugins";
|
|
||||||
string resourcesPath, localesDir;
|
|
||||||
var platformDir = baseDir;
|
|
||||||
|
|
||||||
#if UNITY_EDITOR_WIN
|
|
||||||
#if UNITY_EDITOR_64
|
|
||||||
platformDir += "/w64";
|
|
||||||
#else
|
|
||||||
platformDir += "/w32";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
resourcesPath = platformDir + "/CEFResources";
|
|
||||||
localesDir = resourcesPath + "/locales";
|
|
||||||
|
|
||||||
//Silly MS.
|
|
||||||
resourcesPath = resourcesPath.Replace("/", "\\");
|
|
||||||
localesDir = localesDir.Replace("/", "\\");
|
|
||||||
platformDir = platformDir.Replace("/", "\\");
|
|
||||||
|
|
||||||
var subprocessFile = platformDir + "/" + SlaveExecutable + ".exe";
|
|
||||||
var logFile = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "/Unity/Editor/Editor.log";
|
|
||||||
|
|
||||||
#elif UNITY_EDITOR_LINUX
|
|
||||||
#if UNITY_EDITOR_64
|
|
||||||
platformDir += "/l64";
|
|
||||||
#else
|
|
||||||
platformDir += "/w32";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
resourcesPath = platformDir + "/CEFResources";
|
|
||||||
localesDir = resourcesPath + "/locales";
|
|
||||||
|
|
||||||
var subprocessFile = platformDir + "/" + SlaveExecutable;
|
|
||||||
var logFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/unity3d/Editor.log";
|
|
||||||
|
|
||||||
#elif UNITY_EDITOR_OSX
|
|
||||||
platformDir += "/m64";
|
|
||||||
|
|
||||||
resourcesPath = platformDir + "/BrowserLib.app/Contents/Frameworks/Chromium Embedded Framework.framework/Resources";
|
|
||||||
localesDir = resourcesPath;
|
|
||||||
|
|
||||||
//Chromium's base::mac::GetAppBundlePath will walk up the tree until it finds an ".app" folder and start
|
|
||||||
//looking for pieces from there. That's why everything is hidden in a fake "BrowserLib.app"
|
|
||||||
//folder that's not actually an app.
|
|
||||||
var subprocessFile = platformDir + "/BrowserLib.app/Contents/Frameworks/" + SlaveExecutable + ".app/Contents/MacOS/" + SlaveExecutable;
|
|
||||||
|
|
||||||
var logFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/Library/Logs/Unity/Editor.log";
|
|
||||||
#else
|
|
||||||
//If you want to build your app without ZFBrowser on some platforms change this to an exception or
|
|
||||||
//tweak the .asmdef
|
|
||||||
#error ZFBrowser is not supported on this platform
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
return new CEFDirs() {
|
|
||||||
resourcesPath = resourcesPath,
|
|
||||||
binariesPath = platformDir,
|
|
||||||
localesPath = localesDir,
|
|
||||||
subprocessFile = subprocessFile,
|
|
||||||
logFile = logFile,
|
|
||||||
};
|
|
||||||
|
|
||||||
#elif UNITY_STANDALONE_WIN
|
|
||||||
var resourcesPath = Application.dataPath + "/Plugins";
|
|
||||||
|
|
||||||
var logFile = Application.dataPath + "/output_log.txt";
|
|
||||||
#if UNITY_2017_2_OR_NEWER
|
|
||||||
var appLowDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low/" + Application.companyName + "/" + Application.productName;
|
|
||||||
if (Directory.Exists(appLowDir)) {
|
|
||||||
logFile = appLowDir + "/output_log.txt";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return new CEFDirs() {
|
|
||||||
resourcesPath = resourcesPath,
|
|
||||||
binariesPath = resourcesPath,
|
|
||||||
localesPath = resourcesPath + "/locales",
|
|
||||||
subprocessFile = resourcesPath + "/" + SlaveExecutable + ".exe",
|
|
||||||
logFile = logFile,
|
|
||||||
};
|
|
||||||
#elif UNITY_STANDALONE_LINUX
|
|
||||||
var resourcesPath = Application.dataPath + "/Plugins";
|
|
||||||
return new CEFDirs() {
|
|
||||||
resourcesPath = resourcesPath,
|
|
||||||
binariesPath = resourcesPath,
|
|
||||||
localesPath = resourcesPath + "/locales",
|
|
||||||
subprocessFile = resourcesPath + "/" + SlaveExecutable,
|
|
||||||
logFile = "/dev/null",
|
|
||||||
// Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/unity3d/" +
|
|
||||||
// Application.companyName + "/" + Application.productName + "/Player.log",
|
|
||||||
};
|
|
||||||
#elif UNITY_STANDALONE_OSX
|
|
||||||
return new CEFDirs() {
|
|
||||||
resourcesPath = Application.dataPath + "/Frameworks/Chromium Embedded Framework.framework/Resources",
|
|
||||||
binariesPath = Application.dataPath + "/Plugins",
|
|
||||||
localesPath = Application.dataPath + "/Frameworks/Chromium Embedded Framework.framework/Resources",
|
|
||||||
subprocessFile = Application.dataPath + "/Frameworks/ZFGameBrowser.app/Contents/MacOS/" + SlaveExecutable,
|
|
||||||
logFile = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/Library/Logs/Unity/Player.log",
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
#error Web textures are not supported on this platform
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 6fee78664a3055740bb2c20fa9c2d38a
|
|
||||||
timeCreated: 1454001980
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,21 +0,0 @@
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handler for creating new windows. (See Browser.NewWindowAction.NewBrowser)
|
|
||||||
///
|
|
||||||
/// When the browser needs to open a new window, myHandler.CreateBrowser will be called. Create a
|
|
||||||
/// new browser how and where you will, then return it. The new Browser will be filled with
|
|
||||||
/// the new page.
|
|
||||||
///
|
|
||||||
/// Call browser.SetNewWindowHandler(NewWindowAction.NewBrowser, myClass) to have a browser use it.
|
|
||||||
/// </summary>
|
|
||||||
public interface INewWindowHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Browser object to hold a new page.
|
|
||||||
* The returned Browser object will then be linked and load the new page.
|
|
||||||
*/
|
|
||||||
Browser CreateBrowser(Browser parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 3ee8fedccb68f1a46965dec4d88bd321
|
|
||||||
timeCreated: 1449697159
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,269 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stand-in class for a JavaScript value that can be one of many different types.
|
|
||||||
*
|
|
||||||
* Bad lookups are safe. That is, if you try to look up something that doesn't exist you will not get an exception,
|
|
||||||
* but an "invalid" node. Use Check() if you want an exception on invalid lookups:
|
|
||||||
*
|
|
||||||
* var node = JSONNode.Parse(@"{""a"":1}");
|
|
||||||
* node["a"].IsValid == true;
|
|
||||||
* node["bob"].IsValid == false
|
|
||||||
* node["bob"]["foo"].IsValid == false //but doesn't throw an exception
|
|
||||||
* node["a"].Check() //okay
|
|
||||||
* node["bob"].Check() //throw exception
|
|
||||||
*
|
|
||||||
* Values can be implicitly converted to JSONNodes and back. That means you don't have to use properties like
|
|
||||||
* "IntValue" and "StringValue". Simply try to use the node as that type and it will convert to the value
|
|
||||||
* if it's that type or return a default value if it isn't:
|
|
||||||
*
|
|
||||||
* var node = JSONNode.Parse(@"{""a"":1, ""b"": ""apples""}");
|
|
||||||
* int a = node["a"];
|
|
||||||
* a == 1;
|
|
||||||
* string b = node["b"];
|
|
||||||
* b == "apples";
|
|
||||||
* string str = node["a"];
|
|
||||||
* str == null; //null is the default value for a string.
|
|
||||||
*
|
|
||||||
* You can also use new JSONNode(value) for many different types, though often it's easier to just assign a
|
|
||||||
* value and let it auto-convert.
|
|
||||||
*
|
|
||||||
* Because they act a little special, use node.IsNull and node.IsValid to check for null and invalid values.
|
|
||||||
* Real null still acts like null, though, so use JSONNode.NullNode to create a "null" JSONNode.
|
|
||||||
* You can also use JSONNode.InvalidNode to get an invalid JSONNode outright.
|
|
||||||
*
|
|
||||||
* Note that, while reading blind is safe, assignment is not. Attempting to assign object keys to an integer, for example,
|
|
||||||
* will throw an exception. To append to an array, call .Add() or assign to -1. To remove an object key or array element,
|
|
||||||
* assign JSONNode.InvalidNode to it.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class JSONNode {
|
|
||||||
/** Parses the given JSON document to a JSONNode. Throws a SerializationException on parse error. */
|
|
||||||
public static JSONNode Parse(string json) {
|
|
||||||
return JSONParser.Parse(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly JSONNode InvalidNode = new JSONNode(NodeType.Invalid);
|
|
||||||
public static readonly JSONNode NullNode = new JSONNode(NodeType.Null);
|
|
||||||
|
|
||||||
public enum NodeType {
|
|
||||||
/** Getting this value would result in undefined or ordinarily throw some type of exception trying to fetch it. */
|
|
||||||
Invalid,
|
|
||||||
String,
|
|
||||||
Number,
|
|
||||||
Object,
|
|
||||||
Array,
|
|
||||||
Bool,
|
|
||||||
Null,
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeType _type = NodeType.Invalid;
|
|
||||||
private string _stringValue;
|
|
||||||
private double _numberValue;
|
|
||||||
private Dictionary<string, JSONNode> _objectValue;
|
|
||||||
private List<JSONNode> _arrayValue;
|
|
||||||
private bool _boolValue;
|
|
||||||
|
|
||||||
public JSONNode(NodeType type = NodeType.Null) {
|
|
||||||
this._type = type;
|
|
||||||
if (type == NodeType.Object) _objectValue = new Dictionary<string, JSONNode>();
|
|
||||||
else if (type == NodeType.Array) _arrayValue = new List<JSONNode>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONNode(string value) {
|
|
||||||
this._type = NodeType.String;
|
|
||||||
_stringValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONNode(double value) {
|
|
||||||
this._type = NodeType.Number;
|
|
||||||
_numberValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONNode(Dictionary<string, JSONNode> value) {
|
|
||||||
this._type = NodeType.Object;
|
|
||||||
_objectValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONNode(List<JSONNode> value) {
|
|
||||||
this._type = NodeType.Array;
|
|
||||||
_arrayValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONNode(bool value) {
|
|
||||||
this._type = NodeType.Bool;
|
|
||||||
_boolValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NodeType Type { get { return _type; } }
|
|
||||||
|
|
||||||
public bool IsValid {
|
|
||||||
get { return _type != NodeType.Invalid; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the node is valid. If not, throws an exception.
|
|
||||||
* Returns {this}, which allows you to add this statement inline in you expressions.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* var node = data["key1"][1].Check();
|
|
||||||
* int val = data["maxSize"].Check()["elements"][3];
|
|
||||||
*/
|
|
||||||
public JSONNode Check() {
|
|
||||||
if (_type == NodeType.Invalid) throw new InvalidJSONNodeException();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static implicit operator string(JSONNode n) {
|
|
||||||
return n._type == NodeType.String ? n._stringValue : null;
|
|
||||||
}
|
|
||||||
public static implicit operator JSONNode(string v) {
|
|
||||||
return new JSONNode(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator int(JSONNode n) {
|
|
||||||
return n._type == NodeType.Number ? (int)n._numberValue : 0;
|
|
||||||
}
|
|
||||||
public static implicit operator JSONNode(int v) {
|
|
||||||
return new JSONNode(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator float(JSONNode n) {
|
|
||||||
return n._type == NodeType.Number ? (float)n._numberValue : 0;
|
|
||||||
}
|
|
||||||
public static implicit operator JSONNode(float v) {
|
|
||||||
return new JSONNode(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator double(JSONNode n) {
|
|
||||||
return n._type == NodeType.Number ? n._numberValue : 0;
|
|
||||||
}
|
|
||||||
public static implicit operator JSONNode(double v) {
|
|
||||||
return new JSONNode(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setter/getter for keys on an object. All keys are strings.
|
|
||||||
* Assign JSONNode.InvalidValue to delete a key.
|
|
||||||
*/
|
|
||||||
public JSONNode this[string k] {
|
|
||||||
get {
|
|
||||||
if (_type == NodeType.Object) {
|
|
||||||
JSONNode ret;
|
|
||||||
if (_objectValue.TryGetValue(k, out ret)) return ret;
|
|
||||||
}
|
|
||||||
return InvalidNode;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if (_type != NodeType.Object) throw new InvalidJSONNodeException();
|
|
||||||
if (value._type == NodeType.Invalid) _objectValue.Remove(k);
|
|
||||||
else _objectValue[k] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static implicit operator Dictionary<string, JSONNode>(JSONNode n) {
|
|
||||||
return n._type == NodeType.Object ? n._objectValue : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setter/getter for indicies on an array.
|
|
||||||
* Assign JSONNode.InvalidValue to delete a key.
|
|
||||||
* Assign to "-1" to append to the end.
|
|
||||||
*/
|
|
||||||
public JSONNode this[int idx] {
|
|
||||||
get {
|
|
||||||
if (_type == NodeType.Array && idx >= 0 && idx < _arrayValue.Count) {
|
|
||||||
return _arrayValue[idx];
|
|
||||||
}
|
|
||||||
return InvalidNode;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if (_type != NodeType.Array) throw new InvalidJSONNodeException();
|
|
||||||
|
|
||||||
if (idx == -1) {
|
|
||||||
if (value._type == NodeType.Invalid) {
|
|
||||||
_arrayValue.RemoveAt(_arrayValue.Count - 1);
|
|
||||||
} else {
|
|
||||||
_arrayValue.Add(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (value._type == NodeType.Invalid) {
|
|
||||||
_arrayValue.RemoveAt(idx);
|
|
||||||
} else {
|
|
||||||
_arrayValue[idx] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static implicit operator List<JSONNode>(JSONNode n) {
|
|
||||||
return n._type == NodeType.Array ? n._arrayValue : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Adds an items if the node is an array. */
|
|
||||||
public void Add(JSONNode item) {
|
|
||||||
if (_type != NodeType.Array) throw new InvalidJSONNodeException();
|
|
||||||
_arrayValue.Add(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If we are an array or object, returns the size, otherwise returns 0. */
|
|
||||||
public int Count {
|
|
||||||
get {
|
|
||||||
switch (_type) {
|
|
||||||
case NodeType.Array: return _arrayValue.Count;
|
|
||||||
case NodeType.Object: return _objectValue.Count;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** True if the value of this node is exactly null. */
|
|
||||||
public bool IsNull {
|
|
||||||
get { return _type == NodeType.Null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator bool(JSONNode n) {
|
|
||||||
return n._type == NodeType.Bool ? n._boolValue : false;
|
|
||||||
}
|
|
||||||
public static implicit operator JSONNode(bool v) {
|
|
||||||
return new JSONNode(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a native value representation of our value. */
|
|
||||||
public object Value {
|
|
||||||
get {
|
|
||||||
switch (_type) {
|
|
||||||
case NodeType.Invalid:
|
|
||||||
Check();
|
|
||||||
return null;//we don't get here.
|
|
||||||
case NodeType.String:
|
|
||||||
return _stringValue;
|
|
||||||
case NodeType.Number:
|
|
||||||
return _numberValue;
|
|
||||||
case NodeType.Object:
|
|
||||||
return _objectValue;
|
|
||||||
case NodeType.Array:
|
|
||||||
return _arrayValue;
|
|
||||||
case NodeType.Bool:
|
|
||||||
return _boolValue;
|
|
||||||
case NodeType.Null:
|
|
||||||
return null;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Serializes the JSON node and returns a JSON string. */
|
|
||||||
public string AsJSON {
|
|
||||||
get {
|
|
||||||
return JSONParser.Serialize(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class InvalidJSONNodeException : Exception {}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 0ae4f653ec4655d47b160575a231ddc9
|
|
||||||
timeCreated: 1447780609
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,541 +0,0 @@
|
||||||
/*
|
|
||||||
* This is a customized parser, based on "SimpleJson.cs", for JSONNode
|
|
||||||
*
|
|
||||||
* SimpleJson.cs is open source software and this entire file may be dealt with as described below:
|
|
||||||
*/
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
// <copyright file="SimpleJson.cs" company="The Outercurve Foundation">
|
|
||||||
// Copyright (c) 2011, The Outercurve Foundation, 2015 Zen Fulcrum LLC
|
|
||||||
//
|
|
||||||
// Licensed under the MIT License (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.opensource.org/licenses/mit-license.php
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
// </copyright>
|
|
||||||
// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author>
|
|
||||||
// <website>https://github.com/facebook-csharp-sdk/simple-json</website>
|
|
||||||
//-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Runtime.Serialization;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
/// <summary>
|
|
||||||
/// This class encodes and decodes JSON strings.
|
|
||||||
/// Spec. details, see http://www.json.org/
|
|
||||||
///
|
|
||||||
/// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>).
|
|
||||||
/// All numbers are parsed to doubles.
|
|
||||||
/// </summary>
|
|
||||||
internal static class JSONParser {
|
|
||||||
private const int TOKEN_NONE = 0;
|
|
||||||
private const int TOKEN_CURLY_OPEN = 1;
|
|
||||||
private const int TOKEN_CURLY_CLOSE = 2;
|
|
||||||
private const int TOKEN_SQUARED_OPEN = 3;
|
|
||||||
private const int TOKEN_SQUARED_CLOSE = 4;
|
|
||||||
private const int TOKEN_COLON = 5;
|
|
||||||
private const int TOKEN_COMMA = 6;
|
|
||||||
private const int TOKEN_STRING = 7;
|
|
||||||
private const int TOKEN_NUMBER = 8;
|
|
||||||
private const int TOKEN_TRUE = 9;
|
|
||||||
private const int TOKEN_FALSE = 10;
|
|
||||||
private const int TOKEN_NULL = 11;
|
|
||||||
private const int BUILDER_CAPACITY = 2000;
|
|
||||||
private static readonly char[] EscapeTable;
|
|
||||||
private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' };
|
|
||||||
// private static readonly string EscapeCharactersString = new string(EscapeCharacters);
|
|
||||||
static JSONParser() {
|
|
||||||
EscapeTable = new char[93];
|
|
||||||
EscapeTable['"'] = '"';
|
|
||||||
EscapeTable['\\'] = '\\';
|
|
||||||
EscapeTable['\b'] = 'b';
|
|
||||||
EscapeTable['\f'] = 'f';
|
|
||||||
EscapeTable['\n'] = 'n';
|
|
||||||
EscapeTable['\r'] = 'r';
|
|
||||||
EscapeTable['\t'] = 't';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses the string json into a value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json">A JSON string.</param>
|
|
||||||
/// <returns>An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false</returns>
|
|
||||||
public static JSONNode Parse(string json) {
|
|
||||||
JSONNode obj;
|
|
||||||
if (TryDeserializeObject(json, out obj))
|
|
||||||
return obj;
|
|
||||||
throw new SerializationException("Invalid JSON string");
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Try parsing the json string into a value.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="json">
|
|
||||||
/// A JSON string.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="obj">
|
|
||||||
/// The object.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
|
||||||
/// Returns true if successfull otherwise false.
|
|
||||||
/// </returns>
|
|
||||||
public static bool TryDeserializeObject(string json, out JSONNode obj) {
|
|
||||||
bool success = true;
|
|
||||||
if (json != null) {
|
|
||||||
char[] charArray = json.ToCharArray();
|
|
||||||
int index = 0;
|
|
||||||
obj = ParseValue(charArray, ref index, ref success);
|
|
||||||
} else
|
|
||||||
obj = null;
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string EscapeToJavascriptString(string jsonString) {
|
|
||||||
if (string.IsNullOrEmpty(jsonString))
|
|
||||||
return jsonString;
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
char c;
|
|
||||||
for (int i = 0; i < jsonString.Length; ) {
|
|
||||||
c = jsonString[i++];
|
|
||||||
if (c == '\\') {
|
|
||||||
int remainingLength = jsonString.Length - i;
|
|
||||||
if (remainingLength >= 2) {
|
|
||||||
char lookahead = jsonString[i];
|
|
||||||
if (lookahead == '\\') {
|
|
||||||
sb.Append('\\');
|
|
||||||
++i;
|
|
||||||
} else if (lookahead == '"') {
|
|
||||||
sb.Append("\"");
|
|
||||||
++i;
|
|
||||||
} else if (lookahead == 't') {
|
|
||||||
sb.Append('\t');
|
|
||||||
++i;
|
|
||||||
} else if (lookahead == 'b') {
|
|
||||||
sb.Append('\b');
|
|
||||||
++i;
|
|
||||||
} else if (lookahead == 'n') {
|
|
||||||
sb.Append('\n');
|
|
||||||
++i;
|
|
||||||
} else if (lookahead == 'r') {
|
|
||||||
sb.Append('\r');
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sb.Append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSONNode ParseObject(char[] json, ref int index, ref bool success) {
|
|
||||||
JSONNode table = new JSONNode(JSONNode.NodeType.Object);
|
|
||||||
int token;
|
|
||||||
// {
|
|
||||||
NextToken(json, ref index);
|
|
||||||
bool done = false;
|
|
||||||
while (!done) {
|
|
||||||
token = LookAhead(json, index);
|
|
||||||
if (token == TOKEN_NONE) {
|
|
||||||
success = false;
|
|
||||||
return null;
|
|
||||||
} else if (token == TOKEN_COMMA)
|
|
||||||
NextToken(json, ref index);
|
|
||||||
else if (token == TOKEN_CURLY_CLOSE) {
|
|
||||||
NextToken(json, ref index);
|
|
||||||
return table;
|
|
||||||
} else {
|
|
||||||
// name
|
|
||||||
string name = ParseString(json, ref index, ref success);
|
|
||||||
if (!success) {
|
|
||||||
success = false;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// :
|
|
||||||
token = NextToken(json, ref index);
|
|
||||||
if (token != TOKEN_COLON) {
|
|
||||||
success = false;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// value
|
|
||||||
JSONNode value = ParseValue(json, ref index, ref success);
|
|
||||||
if (!success) {
|
|
||||||
success = false;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
table[name] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSONNode ParseArray(char[] json, ref int index, ref bool success) {
|
|
||||||
JSONNode array = new JSONNode(JSONNode.NodeType.Array);
|
|
||||||
// [
|
|
||||||
NextToken(json, ref index);
|
|
||||||
bool done = false;
|
|
||||||
while (!done) {
|
|
||||||
int token = LookAhead(json, index);
|
|
||||||
if (token == TOKEN_NONE) {
|
|
||||||
success = false;
|
|
||||||
return null;
|
|
||||||
} else if (token == TOKEN_COMMA)
|
|
||||||
NextToken(json, ref index);
|
|
||||||
else if (token == TOKEN_SQUARED_CLOSE) {
|
|
||||||
NextToken(json, ref index);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
JSONNode value = ParseValue(json, ref index, ref success);
|
|
||||||
if (!success)
|
|
||||||
return null;
|
|
||||||
array.Add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSONNode ParseValue(char[] json, ref int index, ref bool success) {
|
|
||||||
switch (LookAhead(json, index)) {
|
|
||||||
case TOKEN_STRING:
|
|
||||||
return ParseString(json, ref index, ref success);
|
|
||||||
case TOKEN_NUMBER:
|
|
||||||
return ParseNumber(json, ref index, ref success);
|
|
||||||
case TOKEN_CURLY_OPEN:
|
|
||||||
return ParseObject(json, ref index, ref success);
|
|
||||||
case TOKEN_SQUARED_OPEN:
|
|
||||||
return ParseArray(json, ref index, ref success);
|
|
||||||
case TOKEN_TRUE:
|
|
||||||
NextToken(json, ref index);
|
|
||||||
return true;
|
|
||||||
case TOKEN_FALSE:
|
|
||||||
NextToken(json, ref index);
|
|
||||||
return false;
|
|
||||||
case TOKEN_NULL:
|
|
||||||
NextToken(json, ref index);
|
|
||||||
return JSONNode.NullNode;
|
|
||||||
case TOKEN_NONE:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
success = false;
|
|
||||||
return JSONNode.InvalidNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSONNode ParseString(char[] json, ref int index, ref bool success) {
|
|
||||||
StringBuilder s = new StringBuilder(BUILDER_CAPACITY);
|
|
||||||
char c;
|
|
||||||
EatWhitespace(json, ref index);
|
|
||||||
// "
|
|
||||||
c = json[index++];
|
|
||||||
bool complete = false;
|
|
||||||
while (!complete) {
|
|
||||||
if (index == json.Length)
|
|
||||||
break;
|
|
||||||
c = json[index++];
|
|
||||||
if (c == '"') {
|
|
||||||
complete = true;
|
|
||||||
break;
|
|
||||||
} else if (c == '\\') {
|
|
||||||
if (index == json.Length)
|
|
||||||
break;
|
|
||||||
c = json[index++];
|
|
||||||
if (c == '"')
|
|
||||||
s.Append('"');
|
|
||||||
else if (c == '\\')
|
|
||||||
s.Append('\\');
|
|
||||||
else if (c == '/')
|
|
||||||
s.Append('/');
|
|
||||||
else if (c == 'b')
|
|
||||||
s.Append('\b');
|
|
||||||
else if (c == 'f')
|
|
||||||
s.Append('\f');
|
|
||||||
else if (c == 'n')
|
|
||||||
s.Append('\n');
|
|
||||||
else if (c == 'r')
|
|
||||||
s.Append('\r');
|
|
||||||
else if (c == 't')
|
|
||||||
s.Append('\t');
|
|
||||||
else if (c == 'u') {
|
|
||||||
int remainingLength = json.Length - index;
|
|
||||||
if (remainingLength >= 4) {
|
|
||||||
// parse the 32 bit hex into an integer codepoint
|
|
||||||
uint codePoint;
|
|
||||||
if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint)))
|
|
||||||
return "";
|
|
||||||
// convert the integer codepoint to a unicode char and add to string
|
|
||||||
if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate
|
|
||||||
{
|
|
||||||
index += 4; // skip 4 chars
|
|
||||||
remainingLength = json.Length - index;
|
|
||||||
if (remainingLength >= 6) {
|
|
||||||
uint lowCodePoint;
|
|
||||||
if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) {
|
|
||||||
if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate
|
|
||||||
{
|
|
||||||
s.Append((char)codePoint);
|
|
||||||
s.Append((char)lowCodePoint);
|
|
||||||
index += 6; // skip 6 chars
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
success = false; // invalid surrogate pair
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
s.Append(ConvertFromUtf32((int)codePoint));
|
|
||||||
// skip 4 chars
|
|
||||||
index += 4;
|
|
||||||
} else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
s.Append(c);
|
|
||||||
}
|
|
||||||
if (!complete) {
|
|
||||||
success = false;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return s.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ConvertFromUtf32(int utf32) {
|
|
||||||
// http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm
|
|
||||||
if (utf32 < 0 || utf32 > 0x10FFFF)
|
|
||||||
throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF.");
|
|
||||||
if (0xD800 <= utf32 && utf32 <= 0xDFFF)
|
|
||||||
throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range.");
|
|
||||||
if (utf32 < 0x10000)
|
|
||||||
return new string((char)utf32, 1);
|
|
||||||
utf32 -= 0x10000;
|
|
||||||
return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSONNode ParseNumber(char[] json, ref int index, ref bool success) {
|
|
||||||
EatWhitespace(json, ref index);
|
|
||||||
int lastIndex = GetLastIndexOfNumber(json, index);
|
|
||||||
int charLength = (lastIndex - index) + 1;
|
|
||||||
JSONNode returnNumber;
|
|
||||||
string str = new string(json, index, charLength);
|
|
||||||
if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) {
|
|
||||||
double number;
|
|
||||||
success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
|
|
||||||
returnNumber = number;
|
|
||||||
} else {
|
|
||||||
long number;
|
|
||||||
success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
|
|
||||||
returnNumber = number;
|
|
||||||
}
|
|
||||||
index = lastIndex + 1;
|
|
||||||
return returnNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int GetLastIndexOfNumber(char[] json, int index) {
|
|
||||||
int lastIndex;
|
|
||||||
for (lastIndex = index; lastIndex < json.Length; lastIndex++)
|
|
||||||
if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break;
|
|
||||||
return lastIndex - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void EatWhitespace(char[] json, ref int index) {
|
|
||||||
for (; index < json.Length; index++)
|
|
||||||
if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break;
|
|
||||||
}
|
|
||||||
static int LookAhead(char[] json, int index) {
|
|
||||||
int saveIndex = index;
|
|
||||||
return NextToken(json, ref saveIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int NextToken(char[] json, ref int index) {
|
|
||||||
EatWhitespace(json, ref index);
|
|
||||||
if (index == json.Length)
|
|
||||||
return TOKEN_NONE;
|
|
||||||
char c = json[index];
|
|
||||||
index++;
|
|
||||||
switch (c) {
|
|
||||||
case '{':
|
|
||||||
return TOKEN_CURLY_OPEN;
|
|
||||||
case '}':
|
|
||||||
return TOKEN_CURLY_CLOSE;
|
|
||||||
case '[':
|
|
||||||
return TOKEN_SQUARED_OPEN;
|
|
||||||
case ']':
|
|
||||||
return TOKEN_SQUARED_CLOSE;
|
|
||||||
case ',':
|
|
||||||
return TOKEN_COMMA;
|
|
||||||
case '"':
|
|
||||||
return TOKEN_STRING;
|
|
||||||
case '0':
|
|
||||||
case '1':
|
|
||||||
case '2':
|
|
||||||
case '3':
|
|
||||||
case '4':
|
|
||||||
case '5':
|
|
||||||
case '6':
|
|
||||||
case '7':
|
|
||||||
case '8':
|
|
||||||
case '9':
|
|
||||||
case '-':
|
|
||||||
return TOKEN_NUMBER;
|
|
||||||
case ':':
|
|
||||||
return TOKEN_COLON;
|
|
||||||
}
|
|
||||||
index--;
|
|
||||||
int remainingLength = json.Length - index;
|
|
||||||
// false
|
|
||||||
if (remainingLength >= 5) {
|
|
||||||
if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') {
|
|
||||||
index += 5;
|
|
||||||
return TOKEN_FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// true
|
|
||||||
if (remainingLength >= 4) {
|
|
||||||
if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') {
|
|
||||||
index += 4;
|
|
||||||
return TOKEN_TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// null
|
|
||||||
if (remainingLength >= 4) {
|
|
||||||
if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') {
|
|
||||||
index += 4;
|
|
||||||
return TOKEN_NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TOKEN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Serialize(JSONNode node) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
var success = SerializeValue(node, sb);
|
|
||||||
if (!success) throw new SerializationException("Failed to serialize JSON");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool SerializeValue(JSONNode value, StringBuilder builder) {
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
// if (value == null) {
|
|
||||||
// builder.Append("null");
|
|
||||||
// return success;
|
|
||||||
// }
|
|
||||||
|
|
||||||
switch (value.Type) {
|
|
||||||
case JSONNode.NodeType.String:
|
|
||||||
success = SerializeString(value, builder);
|
|
||||||
break;
|
|
||||||
case JSONNode.NodeType.Object: {
|
|
||||||
Dictionary<String, JSONNode> dict = value;
|
|
||||||
success = SerializeObject(dict.Keys, dict.Values, builder);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case JSONNode.NodeType.Array:
|
|
||||||
success = SerializeArray((List<JSONNode>)value, builder);
|
|
||||||
break;
|
|
||||||
case JSONNode.NodeType.Number:
|
|
||||||
success = SerializeNumber(value, builder);
|
|
||||||
break;
|
|
||||||
case JSONNode.NodeType.Bool:
|
|
||||||
builder.Append(value ? "true" : "false");
|
|
||||||
break;
|
|
||||||
case JSONNode.NodeType.Null:
|
|
||||||
builder.Append("null");
|
|
||||||
break;
|
|
||||||
case JSONNode.NodeType.Invalid:
|
|
||||||
throw new SerializationException("Cannot serialize invalid JSONNode");
|
|
||||||
default:
|
|
||||||
throw new SerializationException("Unknown JSONNode type");
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool SerializeObject(IEnumerable<string> keys, IEnumerable<JSONNode> values, StringBuilder builder) {
|
|
||||||
builder.Append("{");
|
|
||||||
var ke = keys.GetEnumerator();
|
|
||||||
var ve = values.GetEnumerator();
|
|
||||||
bool first = true;
|
|
||||||
while (ke.MoveNext() && ve.MoveNext()) {
|
|
||||||
var key = ke.Current;
|
|
||||||
var value = ve.Current;
|
|
||||||
if (!first)
|
|
||||||
builder.Append(",");
|
|
||||||
string stringKey = key;
|
|
||||||
if (stringKey != null)
|
|
||||||
SerializeString(stringKey, builder);
|
|
||||||
else
|
|
||||||
if (!SerializeValue(value, builder)) return false;
|
|
||||||
builder.Append(":");
|
|
||||||
if (!SerializeValue(value, builder))
|
|
||||||
return false;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
builder.Append("}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool SerializeArray(IEnumerable<JSONNode> anArray, StringBuilder builder) {
|
|
||||||
builder.Append("[");
|
|
||||||
bool first = true;
|
|
||||||
foreach (var value in anArray) {
|
|
||||||
if (!first)
|
|
||||||
builder.Append(",");
|
|
||||||
if (!SerializeValue(value, builder))
|
|
||||||
return false;
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
builder.Append("]");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool SerializeString(string aString, StringBuilder builder) {
|
|
||||||
// Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged)
|
|
||||||
if (aString.IndexOfAny(EscapeCharacters) == -1) {
|
|
||||||
builder.Append('"');
|
|
||||||
builder.Append(aString);
|
|
||||||
builder.Append('"');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
builder.Append('"');
|
|
||||||
int safeCharacterCount = 0;
|
|
||||||
char[] charArray = aString.ToCharArray();
|
|
||||||
for (int i = 0; i < charArray.Length; i++) {
|
|
||||||
char c = charArray[i];
|
|
||||||
// Non ascii characters are fine, buffer them up and send them to the builder
|
|
||||||
// in larger chunks if possible. The escape table is a 1:1 translation table
|
|
||||||
// with \0 [default(char)] denoting a safe character.
|
|
||||||
if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) {
|
|
||||||
safeCharacterCount++;
|
|
||||||
} else {
|
|
||||||
if (safeCharacterCount > 0) {
|
|
||||||
builder.Append(charArray, i - safeCharacterCount, safeCharacterCount);
|
|
||||||
safeCharacterCount = 0;
|
|
||||||
}
|
|
||||||
builder.Append('\\');
|
|
||||||
builder.Append(EscapeTable[c]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (safeCharacterCount > 0) {
|
|
||||||
builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount);
|
|
||||||
}
|
|
||||||
builder.Append('"');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool SerializeNumber(double number, StringBuilder builder) {
|
|
||||||
builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 8cb978759cbdf48468bc42a00fe0e849
|
|
||||||
timeCreated: 1447780610
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,156 +0,0 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
static class KeyMappings {
|
|
||||||
private static Dictionary<KeyCode, int> mappings = new Dictionary<KeyCode, int>() {
|
|
||||||
//I'm not gonna lie. I just opened http://www.w3.org/2002/09/tests/keys.html
|
|
||||||
//and copied down the values my keyboard produced. >_<
|
|
||||||
{KeyCode.Escape, 27},
|
|
||||||
{KeyCode.F1, 112},
|
|
||||||
{KeyCode.F2, 113},
|
|
||||||
{KeyCode.F3, 114},
|
|
||||||
{KeyCode.F4, 115},
|
|
||||||
{KeyCode.F5, 116},
|
|
||||||
{KeyCode.F6, 117},
|
|
||||||
{KeyCode.F7, 118},
|
|
||||||
{KeyCode.F8, 119},
|
|
||||||
{KeyCode.F9, 120},
|
|
||||||
{KeyCode.F10, 121},
|
|
||||||
{KeyCode.F11, 122},
|
|
||||||
{KeyCode.F12, 123},
|
|
||||||
{KeyCode.SysReq, 44}, {KeyCode.Print, 44},
|
|
||||||
{KeyCode.ScrollLock, 145},
|
|
||||||
{KeyCode.Pause, 19},
|
|
||||||
{KeyCode.BackQuote, 192},
|
|
||||||
|
|
||||||
|
|
||||||
{KeyCode.Alpha0, 48},
|
|
||||||
{KeyCode.Alpha1, 49},
|
|
||||||
{KeyCode.Alpha2, 50},
|
|
||||||
{KeyCode.Alpha3, 51},
|
|
||||||
{KeyCode.Alpha4, 52},
|
|
||||||
{KeyCode.Alpha5, 53},
|
|
||||||
{KeyCode.Alpha6, 54},
|
|
||||||
{KeyCode.Alpha7, 55},
|
|
||||||
{KeyCode.Alpha8, 56},
|
|
||||||
{KeyCode.Alpha9, 57},
|
|
||||||
{KeyCode.Minus, 189},
|
|
||||||
{KeyCode.Equals, 187},
|
|
||||||
{KeyCode.Backspace, 8},
|
|
||||||
|
|
||||||
{KeyCode.Tab, 9},
|
|
||||||
//char keys
|
|
||||||
{KeyCode.LeftBracket, 219},
|
|
||||||
{KeyCode.RightBracket, 221},
|
|
||||||
{KeyCode.Backslash, 220},
|
|
||||||
|
|
||||||
{KeyCode.CapsLock, 20},
|
|
||||||
//char keys
|
|
||||||
{KeyCode.Semicolon, 186},
|
|
||||||
{KeyCode.Quote, 222},
|
|
||||||
{KeyCode.Return, 13},
|
|
||||||
|
|
||||||
{KeyCode.LeftShift, 16},
|
|
||||||
//char keys
|
|
||||||
{KeyCode.Comma, 188},
|
|
||||||
{KeyCode.Period, 190},
|
|
||||||
{KeyCode.Slash, 191},
|
|
||||||
{KeyCode.RightShift, 16},
|
|
||||||
|
|
||||||
{KeyCode.LeftControl, 17},
|
|
||||||
{KeyCode.LeftCommand, 91}, {KeyCode.LeftWindows, 91},
|
|
||||||
{KeyCode.LeftAlt, 18},
|
|
||||||
{KeyCode.Space, 32},
|
|
||||||
{KeyCode.RightAlt, 18},
|
|
||||||
{KeyCode.RightCommand, 92}, {KeyCode.RightWindows, 92},
|
|
||||||
{KeyCode.Menu, 93},
|
|
||||||
{KeyCode.RightControl, 17},
|
|
||||||
|
|
||||||
|
|
||||||
{KeyCode.Insert, 45},
|
|
||||||
{KeyCode.Home, 36},
|
|
||||||
{KeyCode.PageUp, 33},
|
|
||||||
|
|
||||||
{KeyCode.Delete, 46},
|
|
||||||
{KeyCode.End, 35},
|
|
||||||
{KeyCode.PageDown, 34},
|
|
||||||
|
|
||||||
{KeyCode.UpArrow, 38},
|
|
||||||
{KeyCode.LeftArrow, 37},
|
|
||||||
{KeyCode.DownArrow, 40},
|
|
||||||
{KeyCode.RightArrow, 39},
|
|
||||||
|
|
||||||
|
|
||||||
{KeyCode.Numlock, 144},
|
|
||||||
{KeyCode.KeypadDivide, 111},
|
|
||||||
{KeyCode.KeypadMultiply, 106},
|
|
||||||
{KeyCode.KeypadMinus, 109},
|
|
||||||
|
|
||||||
{KeyCode.Keypad7, 103},
|
|
||||||
{KeyCode.Keypad8, 104},
|
|
||||||
{KeyCode.Keypad9, 105},
|
|
||||||
{KeyCode.KeypadPlus, 107},
|
|
||||||
|
|
||||||
{KeyCode.Keypad4, 100},
|
|
||||||
{KeyCode.Keypad5, 101},
|
|
||||||
{KeyCode.Keypad6, 102},
|
|
||||||
|
|
||||||
{KeyCode.Keypad1, 97},
|
|
||||||
{KeyCode.Keypad2, 98},
|
|
||||||
{KeyCode.Keypad3, 99},
|
|
||||||
{KeyCode.KeypadEnter, 13},
|
|
||||||
|
|
||||||
{KeyCode.Keypad0, 96},
|
|
||||||
{KeyCode.KeypadPeriod, 110},
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Dictionary<int, KeyCode> reverseMappings = new Dictionary<int, KeyCode>();
|
|
||||||
|
|
||||||
static KeyMappings() {
|
|
||||||
foreach (var kvp in mappings) {
|
|
||||||
reverseMappings[kvp.Value] = kvp.Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = (int)KeyCode.A; i <= (int)KeyCode.Z; i++) {
|
|
||||||
var key = (KeyCode)i;
|
|
||||||
var keyCode = i - (int)KeyCode.A + 65;
|
|
||||||
mappings[key] = keyCode;
|
|
||||||
reverseMappings[keyCode] = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetWindowsKeyCode(Event ev) {
|
|
||||||
int ukc = (int)ev.keyCode;//unity key code
|
|
||||||
|
|
||||||
//When dealing with characters return the Unicode char as the keycode.
|
|
||||||
if (ukc == 0) {
|
|
||||||
//enter is special.
|
|
||||||
if (ev.character == 10) return 13;
|
|
||||||
|
|
||||||
return ev.character;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (ukc >= (int)KeyCode.A && ukc <= (int)KeyCode.Z) {
|
|
||||||
// return ukc - (int)KeyCode.A + 65;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
if (mappings.TryGetValue(ev.keyCode, out ret)) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Don't recognize it, we'll just have to return something, but it's almost sure to be wrong.
|
|
||||||
Debug.LogWarning("Unknown key mapping: " + ev);
|
|
||||||
return ukc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static KeyCode GetUnityKeyCode(int windowsKeyCode) {
|
|
||||||
KeyCode ret;
|
|
||||||
if (reverseMappings.TryGetValue(windowsKeyCode, out ret)) return ret;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: de63d991f0c0f0b41a32bebdc64b329a
|
|
||||||
timeCreated: 1447440809
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,203 +0,0 @@
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Manages a browser that's been opened in an OS-native window outside the game's main window.
|
|
||||||
/// (Presently only used for OS X.)
|
|
||||||
/// </summary>
|
|
||||||
public class PopUpBrowser : MonoBehaviour, IBrowserUI {
|
|
||||||
private int windowId;
|
|
||||||
private int browserId;
|
|
||||||
|
|
||||||
private List<string> messages = new List<string>();
|
|
||||||
private Browser browser;
|
|
||||||
private BrowserNative.WindowCallbackFunc callbackRef;//don't let this get GC'd
|
|
||||||
private Vector2 delayedResize;
|
|
||||||
|
|
||||||
public static void Create(int possibleBrowserId) {
|
|
||||||
var go = new GameObject("Pop up browser");
|
|
||||||
var browser = go.AddComponent<Browser>();
|
|
||||||
browser.RequestNativeBrowser(possibleBrowserId);
|
|
||||||
|
|
||||||
var pop = go.AddComponent<PopUpBrowser>();
|
|
||||||
pop.callbackRef = pop.HandleWindowMessage;
|
|
||||||
pop.windowId = BrowserNative.zfb_windowCreate("", pop.callbackRef);
|
|
||||||
pop.browserId = possibleBrowserId;
|
|
||||||
pop.BrowserCursor = new BrowserCursor();
|
|
||||||
pop.KeyEvents = new List<Event>();
|
|
||||||
pop.browser = browser;
|
|
||||||
|
|
||||||
pop.StartCoroutine(pop.FixFocus());
|
|
||||||
|
|
||||||
pop.InputSettings = new BrowserInputSettings();
|
|
||||||
|
|
||||||
browser.UIHandler = pop;
|
|
||||||
browser.EnableRendering = false;//rendering is done differently, so don't run the usual code
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleWindowMessage(int windowId, IntPtr data) {
|
|
||||||
var msgJSON = Util.PtrToStringUTF8(data);
|
|
||||||
//if (!msgJSON.Contains("mouseMove")) Debug.Log("Window message: " + msgJSON);
|
|
||||||
|
|
||||||
lock (messages) messages.Add(msgJSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnDestroy() {
|
|
||||||
if (!BrowserNative.SymbolsLoaded) return;
|
|
||||||
BrowserNative.zfb_windowClose(windowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private IEnumerator FixFocus() {
|
|
||||||
//OS X: Magically, new browser windows that are focused don't think they are focused, even though we told them so.
|
|
||||||
//Hack workaround.
|
|
||||||
yield return null;
|
|
||||||
BrowserNative.zfb_setFocused(browserId, false);
|
|
||||||
yield return null;
|
|
||||||
BrowserNative.zfb_setFocused(browserId, true);
|
|
||||||
yield return null;
|
|
||||||
BrowserNative.zfb_setFocused(browserId, KeyboardHasFocus);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InputUpdate() {
|
|
||||||
MouseScroll = Vector2.zero;
|
|
||||||
KeyEvents.Clear();
|
|
||||||
delayedResize = new Vector2(float.NaN, float.NaN);
|
|
||||||
|
|
||||||
lock (messages) {
|
|
||||||
for (int i = 0; i < messages.Count; i++) {
|
|
||||||
HandleMessage(messages[i]);
|
|
||||||
}
|
|
||||||
messages.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!float.IsNaN(delayedResize.x)) {
|
|
||||||
browser.Resize((int)delayedResize.x, (int)delayedResize.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleMessage(string message) {
|
|
||||||
var msg = JSONNode.Parse(message);
|
|
||||||
|
|
||||||
|
|
||||||
switch ((string)msg["type"]) {
|
|
||||||
case "mouseDown":
|
|
||||||
case "mouseUp": {
|
|
||||||
int button = msg["button"];
|
|
||||||
MouseButton flag = 0;
|
|
||||||
if (button == 0) flag = MouseButton.Left;
|
|
||||||
else if (button == 1) flag = MouseButton.Right;
|
|
||||||
else if (button == 2) flag = MouseButton.Middle;
|
|
||||||
|
|
||||||
if (msg["type"] == "mouseDown") MouseButtons |= flag;
|
|
||||||
else MouseButtons &= ~flag;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "mouseMove": {
|
|
||||||
var screenPos = new Vector2(msg["x"], msg["y"]);
|
|
||||||
screenPos.x = screenPos.x / browser.Size.x;
|
|
||||||
screenPos.y = screenPos.y / browser.Size.y;
|
|
||||||
MousePosition = screenPos;
|
|
||||||
//Debug.Log("mouse now at " + screenPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "mouseFocus":
|
|
||||||
MouseHasFocus = msg["focus"];
|
|
||||||
break;
|
|
||||||
case "keyboardFocus":
|
|
||||||
KeyboardHasFocus = msg["focus"];
|
|
||||||
break;
|
|
||||||
case "mouseScroll": {
|
|
||||||
const float mult = 1 / 3f;
|
|
||||||
MouseScroll += new Vector2(msg["x"], msg["y"]) * mult;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "keyDown":
|
|
||||||
case "keyUp": {
|
|
||||||
var ev = new Event();
|
|
||||||
ev.type = msg["type"] == "keyDown" ? EventType.KeyDown : EventType.KeyUp;
|
|
||||||
ev.character = (char)0;
|
|
||||||
ev.keyCode = KeyMappings.GetUnityKeyCode(msg["code"]);
|
|
||||||
SetMods(ev);
|
|
||||||
|
|
||||||
//Debug.Log("Convert wkc " + (int)msg["code"] + " to ukc " + ev.keyCode);
|
|
||||||
|
|
||||||
KeyEvents.Add(ev);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "keyPress": {
|
|
||||||
string characters = msg["characters"];
|
|
||||||
foreach (char c in characters) {
|
|
||||||
var ev = new Event();
|
|
||||||
ev.type = EventType.KeyDown;
|
|
||||||
SetMods(ev);
|
|
||||||
ev.character = c;
|
|
||||||
ev.keyCode = 0;
|
|
||||||
|
|
||||||
KeyEvents.Add(ev);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "resize":
|
|
||||||
//on OS X (editor at least), resizing hangs the update loop, so we suddenly end up with a bajillion resize
|
|
||||||
//messages we were unable to process. Just record it here and when we've processed everything we'll resize
|
|
||||||
delayedResize.x = msg["w"];
|
|
||||||
delayedResize.y = msg["h"];
|
|
||||||
break;
|
|
||||||
case "close":
|
|
||||||
Destroy(gameObject);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Debug.LogWarning("Unknown window event: " + msg.AsJSON);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private bool shiftDown, controlDown, altDown, commandDown;
|
|
||||||
private void SetMods(Event ev) {
|
|
||||||
switch (ev.keyCode) {
|
|
||||||
case KeyCode.LeftShift: case KeyCode.RightShift:
|
|
||||||
shiftDown = ev.type == EventType.KeyDown;
|
|
||||||
break;
|
|
||||||
case KeyCode.LeftControl: case KeyCode.RightControl:
|
|
||||||
controlDown = ev.type == EventType.KeyDown;
|
|
||||||
break;
|
|
||||||
case KeyCode.LeftAlt: case KeyCode.RightAlt:
|
|
||||||
altDown = ev.type == EventType.KeyDown;
|
|
||||||
break;
|
|
||||||
case KeyCode.LeftCommand: case KeyCode.RightCommand:
|
|
||||||
case KeyCode.LeftWindows: case KeyCode.RightWindows:
|
|
||||||
commandDown = ev.type == EventType.KeyDown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.shift = shiftDown;
|
|
||||||
ev.control = controlDown;
|
|
||||||
ev.alt = altDown;
|
|
||||||
ev.command = commandDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update() {
|
|
||||||
if (!BrowserNative.SymbolsLoaded) return;
|
|
||||||
BrowserNative.zfb_windowRender(windowId, browserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MouseHasFocus { get; private set; }
|
|
||||||
public Vector2 MousePosition { get; private set; }
|
|
||||||
public MouseButton MouseButtons { get; private set; }
|
|
||||||
public Vector2 MouseScroll { get; private set; }
|
|
||||||
public bool KeyboardHasFocus { get; private set; }
|
|
||||||
public List<Event> KeyEvents { get; private set; }
|
|
||||||
public BrowserCursor BrowserCursor { get; private set; }
|
|
||||||
public BrowserInputSettings InputSettings { get; private set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 6172fca72ca9adf4da2ac4b4e3136708
|
|
||||||
timeCreated: 1517329102
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,9 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 48b908734323b95409dfd20950349e02
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1479581013
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,45 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser.Promises
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// General extensions to LINQ.
|
|
||||||
/// </summary>
|
|
||||||
public static class EnumerableExt
|
|
||||||
{
|
|
||||||
public static IEnumerable<T> Empty<T>()
|
|
||||||
{
|
|
||||||
return new T[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<T> LazyEach<T>(this IEnumerable<T> source, Action<T> fn)
|
|
||||||
{
|
|
||||||
foreach (var item in source)
|
|
||||||
{
|
|
||||||
fn.Invoke(item);
|
|
||||||
|
|
||||||
yield return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Each<T>(this IEnumerable<T> source, Action<T> fn)
|
|
||||||
{
|
|
||||||
foreach (var item in source)
|
|
||||||
{
|
|
||||||
fn.Invoke(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Each<T>(this IEnumerable<T> source, Action<T, int> fn)
|
|
||||||
{
|
|
||||||
int index = 0;
|
|
||||||
|
|
||||||
foreach (T item in source)
|
|
||||||
{
|
|
||||||
fn.Invoke(item, index);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: b77f44a328c5e2c40ba15d0fc85741fc
|
|
||||||
timeCreated: 1479581013
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,842 +0,0 @@
|
||||||
using ZenFulcrum.EmbeddedBrowser.Promises;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implements a C# promise.
|
|
||||||
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
||||||
///
|
|
||||||
/// This can also be waited on in a Unity coroutine and queried for its value.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPromise<PromisedT>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Set the name of the promise, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<PromisedT> WithName(string name);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// onRejected is called on error.
|
|
||||||
/// </summary>
|
|
||||||
void Done(Action<PromisedT> onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
void Done(Action<PromisedT> onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Complete the promise. Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
void Done();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle errors for the promise.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<PromisedT> Catch(Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Then(Func<PromisedT, IPromise> onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<PromisedT> Then(Action<PromisedT> onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<PromisedT> Then(Action<PromisedT> onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return a new promise with a different value.
|
|
||||||
/// May also change the type of the value.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, ConvertedT> transform);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return a new promise with a different value.
|
|
||||||
/// May also change the type of the value.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use Then instead")]
|
|
||||||
IPromise<ConvertedT> Transform<ConvertedT>(Func<PromisedT, ConvertedT> transform);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// Returns a promise for a collection of the resolved results.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// Converts to a non-value promise.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
IPromise ThenAll(Func<PromisedT, IEnumerable<IPromise>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// Yields the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> ThenRace<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Converts to a non-value promise.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// Yields the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
IPromise ThenRace(Func<PromisedT, IEnumerable<IPromise>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the resulting value if resolved.
|
|
||||||
/// Throws the rejection if rejected.
|
|
||||||
/// Throws an exception if not settled.
|
|
||||||
/// </summary>
|
|
||||||
PromisedT Value { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an enumerable that yields null until the promise is settled.
|
|
||||||
/// ("To WaitFor" like the WaitForXXYY functions Unity provides.)
|
|
||||||
/// Suitable for use with a Unity coroutine's "yield return promise.ToWaitFor()"
|
|
||||||
/// Once it finishes, use promise.Value to retrieve the value/error.
|
|
||||||
///
|
|
||||||
/// If throwOnFail is true, the coroutine will abort on promise rejection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
IEnumerator ToWaitFor(bool abortOnFail = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for a promise that can be rejected.
|
|
||||||
/// </summary>
|
|
||||||
public interface IRejectable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Reject the promise with an exception.
|
|
||||||
/// </summary>
|
|
||||||
void Reject(Exception ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for a promise that can be rejected or resolved.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPendingPromise<PromisedT> : IRejectable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the promise with a particular value.
|
|
||||||
/// </summary>
|
|
||||||
void Resolve(PromisedT value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies the state of a promise.
|
|
||||||
/// </summary>
|
|
||||||
public enum PromiseState
|
|
||||||
{
|
|
||||||
Pending, // The promise is in-flight.
|
|
||||||
Rejected, // The promise has been rejected.
|
|
||||||
Resolved // The promise has been resolved.
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implements a C# promise.
|
|
||||||
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
||||||
/// </summary>
|
|
||||||
public class Promise<PromisedT> : IPromise<PromisedT>, IPendingPromise<PromisedT>, IPromiseInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The exception when the promise is rejected.
|
|
||||||
/// </summary>
|
|
||||||
private Exception rejectionException;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The value when the promises is resolved.
|
|
||||||
/// </summary>
|
|
||||||
private PromisedT resolveValue;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Error handler.
|
|
||||||
/// </summary>
|
|
||||||
private List<RejectHandler> rejectHandlers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completed handlers that accept a value.
|
|
||||||
/// </summary>
|
|
||||||
private List<Action<PromisedT>> resolveCallbacks;
|
|
||||||
private List<IRejectable> resolveRejectables;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ID of the promise, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the promise, when set, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks the current state of the promise.
|
|
||||||
/// </summary>
|
|
||||||
public PromiseState CurState { get; private set; }
|
|
||||||
|
|
||||||
public Promise()
|
|
||||||
{
|
|
||||||
this.CurState = PromiseState.Pending;
|
|
||||||
this.Id = ++Promise.nextPromiseId;
|
|
||||||
|
|
||||||
if (Promise.EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
Promise.pendingPromises.Add(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Promise(Action<Action<PromisedT>, Action<Exception>> resolver)
|
|
||||||
{
|
|
||||||
this.CurState = PromiseState.Pending;
|
|
||||||
this.Id = ++Promise.nextPromiseId;
|
|
||||||
|
|
||||||
if (Promise.EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
Promise.pendingPromises.Add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
resolver(
|
|
||||||
// Resolve
|
|
||||||
value => Resolve(value),
|
|
||||||
|
|
||||||
// Reject
|
|
||||||
ex => Reject(ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Reject(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a rejection handler for this promise.
|
|
||||||
/// </summary>
|
|
||||||
private void AddRejectHandler(Action<Exception> onRejected, IRejectable rejectable)
|
|
||||||
{
|
|
||||||
if (rejectHandlers == null)
|
|
||||||
{
|
|
||||||
rejectHandlers = new List<RejectHandler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectHandlers.Add(new RejectHandler() { callback = onRejected, rejectable = rejectable }); ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolve handler for this promise.
|
|
||||||
/// </summary>
|
|
||||||
private void AddResolveHandler(Action<PromisedT> onResolved, IRejectable rejectable)
|
|
||||||
{
|
|
||||||
if (resolveCallbacks == null)
|
|
||||||
{
|
|
||||||
resolveCallbacks = new List<Action<PromisedT>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolveRejectables == null)
|
|
||||||
{
|
|
||||||
resolveRejectables = new List<IRejectable>();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveCallbacks.Add(onResolved);
|
|
||||||
resolveRejectables.Add(rejectable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke a single handler.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeHandler<T>(Action<T> callback, IRejectable rejectable, T value)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => callback);
|
|
||||||
// Argument.NotNull(() => rejectable);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
callback(value);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
rejectable.Reject(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper function clear out all handlers after resolution or rejection.
|
|
||||||
/// </summary>
|
|
||||||
private void ClearHandlers()
|
|
||||||
{
|
|
||||||
rejectHandlers = null;
|
|
||||||
resolveCallbacks = null;
|
|
||||||
resolveRejectables = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke all reject handlers.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeRejectHandlers(Exception ex)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => ex);
|
|
||||||
|
|
||||||
if (rejectHandlers != null)
|
|
||||||
{
|
|
||||||
rejectHandlers.Each(handler => InvokeHandler(handler.callback, handler.rejectable, ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke all resolve handlers.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeResolveHandlers(PromisedT value)
|
|
||||||
{
|
|
||||||
if (resolveCallbacks != null)
|
|
||||||
{
|
|
||||||
for (int i = 0, maxI = resolveCallbacks.Count; i < maxI; i++) {
|
|
||||||
InvokeHandler(resolveCallbacks[i], resolveRejectables[i], value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reject the promise with an exception.
|
|
||||||
/// </summary>
|
|
||||||
public void Reject(Exception ex)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => ex);
|
|
||||||
|
|
||||||
if (CurState != PromiseState.Pending)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Attempt to reject a promise that is already in state: " + CurState + ", a promise can only be rejected when it is still in state: " + PromiseState.Pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectionException = ex;
|
|
||||||
CurState = PromiseState.Rejected;
|
|
||||||
|
|
||||||
if (Promise.EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
Promise.pendingPromises.Remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeRejectHandlers(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the promise with a particular value.
|
|
||||||
/// </summary>
|
|
||||||
public void Resolve(PromisedT value)
|
|
||||||
{
|
|
||||||
if (CurState != PromiseState.Pending)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Attempt to resolve a promise that is already in state: " + CurState + ", a promise can only be resolved when it is still in state: " + PromiseState.Pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveValue = value;
|
|
||||||
CurState = PromiseState.Resolved;
|
|
||||||
|
|
||||||
if (Promise.EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
Promise.pendingPromises.Remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeResolveHandlers(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// onRejected is called on error.
|
|
||||||
/// </summary>
|
|
||||||
public void Done(Action<PromisedT> onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
Then(onResolved, onRejected)
|
|
||||||
.Catch(ex =>
|
|
||||||
Promise.PropagateUnhandledException(this, ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
public void Done(Action<PromisedT> onResolved)
|
|
||||||
{
|
|
||||||
Then(onResolved)
|
|
||||||
.Catch(ex =>
|
|
||||||
Promise.PropagateUnhandledException(this, ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Complete the promise. Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
public void Done()
|
|
||||||
{
|
|
||||||
Catch(ex =>
|
|
||||||
Promise.PropagateUnhandledException(this, ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the name of the promise, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<PromisedT> WithName(string name)
|
|
||||||
{
|
|
||||||
this.Name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle errors for the promise.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<PromisedT> Catch(Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => onRejected);
|
|
||||||
|
|
||||||
var resultPromise = new Promise<PromisedT>();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action<PromisedT> resolveHandler = v =>
|
|
||||||
{
|
|
||||||
resultPromise.Resolve(v);
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved)
|
|
||||||
{
|
|
||||||
return Then(onResolved, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Then(Func<PromisedT, IPromise> onResolved)
|
|
||||||
{
|
|
||||||
return Then(onResolved, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<PromisedT> Then(Action<PromisedT> onResolved)
|
|
||||||
{
|
|
||||||
return Then(onResolved, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, IPromise<ConvertedT>> onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
// This version of the function must supply an onResolved.
|
|
||||||
// Otherwise there is now way to get the converted value to pass to the resulting promise.
|
|
||||||
// Argument.NotNull(() => onResolved);
|
|
||||||
|
|
||||||
var resultPromise = new Promise<ConvertedT>();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action<PromisedT> resolveHandler = v =>
|
|
||||||
{
|
|
||||||
onResolved(v)
|
|
||||||
.Then(
|
|
||||||
// Should not be necessary to specify the arg type on the next line, but Unity (mono) has an internal compiler error otherwise.
|
|
||||||
(ConvertedT chainedValue) => resultPromise.Resolve(chainedValue),
|
|
||||||
ex => resultPromise.Reject(ex)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
if (onRejected != null)
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Then(Func<PromisedT, IPromise> onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
var resultPromise = new Promise();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action<PromisedT> resolveHandler = v =>
|
|
||||||
{
|
|
||||||
if (onResolved != null)
|
|
||||||
{
|
|
||||||
onResolved(v)
|
|
||||||
.Then(
|
|
||||||
() => resultPromise.Resolve(),
|
|
||||||
ex => resultPromise.Reject(ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resultPromise.Resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
if (onRejected != null)
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<PromisedT> Then(Action<PromisedT> onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
var resultPromise = new Promise<PromisedT>();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action<PromisedT> resolveHandler = v =>
|
|
||||||
{
|
|
||||||
if (onResolved != null)
|
|
||||||
{
|
|
||||||
onResolved(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Resolve(v);
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
if (onRejected != null)
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return a new promise with a different value.
|
|
||||||
/// May also change the type of the value.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> Then<ConvertedT>(Func<PromisedT, ConvertedT> transform)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => transform);
|
|
||||||
return Then(value => Promise<ConvertedT>.Resolved(transform(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return a new promise with a different value.
|
|
||||||
/// May also change the type of the value.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Use Then instead")]
|
|
||||||
public IPromise<ConvertedT> Transform<ConvertedT>(Func<PromisedT, ConvertedT> transform)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => transform);
|
|
||||||
return Then(value => Promise<ConvertedT>.Resolved(transform(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper function to invoke or register resolve/reject handlers.
|
|
||||||
/// </summary>
|
|
||||||
private void ActionHandlers(IRejectable resultPromise, Action<PromisedT> resolveHandler, Action<Exception> rejectHandler)
|
|
||||||
{
|
|
||||||
if (CurState == PromiseState.Resolved)
|
|
||||||
{
|
|
||||||
InvokeHandler(resolveHandler, resultPromise, resolveValue);
|
|
||||||
}
|
|
||||||
else if (CurState == PromiseState.Rejected)
|
|
||||||
{
|
|
||||||
InvokeHandler(rejectHandler, resultPromise, rejectionException);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddResolveHandler(resolveHandler, resultPromise);
|
|
||||||
AddRejectHandler(rejectHandler, resultPromise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// Returns a promise for a collection of the resolved results.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain)
|
|
||||||
{
|
|
||||||
return Then(value => Promise<ConvertedT>.All(chain(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// Converts to a non-value promise.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise ThenAll(Func<PromisedT, IEnumerable<IPromise>> chain)
|
|
||||||
{
|
|
||||||
return Then(value => Promise.All(chain(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when all of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns a promise of a collection of the resolved results.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise<IEnumerable<PromisedT>> All(params IPromise<PromisedT>[] promises)
|
|
||||||
{
|
|
||||||
return All((IEnumerable<IPromise<PromisedT>>)promises); // Cast is required to force use of the other All function.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when all of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns a promise of a collection of the resolved results.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise<IEnumerable<PromisedT>> All(IEnumerable<IPromise<PromisedT>> promises)
|
|
||||||
{
|
|
||||||
var promisesArray = promises.ToArray();
|
|
||||||
if (promisesArray.Length == 0)
|
|
||||||
{
|
|
||||||
return Promise<IEnumerable<PromisedT>>.Resolved(EnumerableExt.Empty<PromisedT>());
|
|
||||||
}
|
|
||||||
|
|
||||||
var remainingCount = promisesArray.Length;
|
|
||||||
var results = new PromisedT[remainingCount];
|
|
||||||
var resultPromise = new Promise<IEnumerable<PromisedT>>();
|
|
||||||
resultPromise.WithName("All");
|
|
||||||
|
|
||||||
promisesArray.Each((promise, index) =>
|
|
||||||
{
|
|
||||||
promise
|
|
||||||
.Catch(ex =>
|
|
||||||
{
|
|
||||||
if (resultPromise.CurState == PromiseState.Pending)
|
|
||||||
{
|
|
||||||
// If a promise errorred and the result promise is still pending, reject it.
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Then(result =>
|
|
||||||
{
|
|
||||||
results[index] = result;
|
|
||||||
|
|
||||||
--remainingCount;
|
|
||||||
if (remainingCount <= 0)
|
|
||||||
{
|
|
||||||
// This will never happen if any of the promises errorred.
|
|
||||||
resultPromise.Resolve(results);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Done();
|
|
||||||
});
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// Yields the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> ThenRace<ConvertedT>(Func<PromisedT, IEnumerable<IPromise<ConvertedT>>> chain)
|
|
||||||
{
|
|
||||||
return Then(value => Promise<ConvertedT>.Race(chain(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Converts to a non-value promise.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// Yields the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise ThenRace(Func<PromisedT, IEnumerable<IPromise>> chain)
|
|
||||||
{
|
|
||||||
return Then(value => Promise.Race(chain(value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public PromisedT Value
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (CurState == PromiseState.Pending) throw new InvalidOperationException("Promise not settled");
|
|
||||||
else if (CurState == PromiseState.Rejected) throw rejectionException;
|
|
||||||
return resolveValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Enumerated<T> : IEnumerator {
|
|
||||||
private Promise<T> promise;
|
|
||||||
private bool abortOnFail;
|
|
||||||
|
|
||||||
public Enumerated(Promise<T> promise, bool abortOnFail) {
|
|
||||||
this.promise = promise;
|
|
||||||
this.abortOnFail = abortOnFail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MoveNext() {
|
|
||||||
if (abortOnFail && promise.CurState == PromiseState.Rejected) {
|
|
||||||
throw promise.rejectionException;
|
|
||||||
}
|
|
||||||
return promise.CurState == PromiseState.Pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset() { }
|
|
||||||
|
|
||||||
public object Current { get { return null; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator ToWaitFor(bool abortOnFail) {
|
|
||||||
var ret = new Enumerated<PromisedT>(this, abortOnFail);
|
|
||||||
//someone will poll for completion, so act like we've been terminated
|
|
||||||
Done(x => {}, ex => {});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise<PromisedT> Race(params IPromise<PromisedT>[] promises)
|
|
||||||
{
|
|
||||||
return Race((IEnumerable<IPromise<PromisedT>>)promises); // Cast is required to force use of the other function.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise<PromisedT> Race(IEnumerable<IPromise<PromisedT>> promises)
|
|
||||||
{
|
|
||||||
var promisesArray = promises.ToArray();
|
|
||||||
if (promisesArray.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("At least 1 input promise must be provided for Race");
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultPromise = new Promise<PromisedT>();
|
|
||||||
resultPromise.WithName("Race");
|
|
||||||
|
|
||||||
promisesArray.Each((promise, index) =>
|
|
||||||
{
|
|
||||||
promise
|
|
||||||
.Catch(ex =>
|
|
||||||
{
|
|
||||||
if (resultPromise.CurState == PromiseState.Pending)
|
|
||||||
{
|
|
||||||
// If a promise errorred and the result promise is still pending, reject it.
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Then(result =>
|
|
||||||
{
|
|
||||||
if (resultPromise.CurState == PromiseState.Pending)
|
|
||||||
{
|
|
||||||
resultPromise.Resolve(result);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Done();
|
|
||||||
});
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a simple value directly into a resolved promise.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise<PromisedT> Resolved(PromisedT promisedValue)
|
|
||||||
{
|
|
||||||
var promise = new Promise<PromisedT>();
|
|
||||||
promise.Resolve(promisedValue);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert an exception directly into a rejected promise.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise<PromisedT> Rejected(Exception ex)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => ex);
|
|
||||||
|
|
||||||
var promise = new Promise<PromisedT>();
|
|
||||||
promise.Reject(ex);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 56222919992acdd489a2deff63c33981
|
|
||||||
timeCreated: 1479581013
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,162 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class that wraps a pending promise with it's predicate and time data
|
|
||||||
/// </summary>
|
|
||||||
internal class PredicateWait
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Predicate for resolving the promise
|
|
||||||
/// </summary>
|
|
||||||
public Func<TimeData, bool> predicate;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time the promise was started
|
|
||||||
/// </summary>
|
|
||||||
public float timeStarted;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The pending promise which is an interface for a promise that can be rejected or resolved.
|
|
||||||
/// </summary>
|
|
||||||
public IPendingPromise pendingPromise;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The time data specific to this pending promise. Includes elapsed time and delta time.
|
|
||||||
/// </summary>
|
|
||||||
public TimeData timeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time data specific to a particular pending promise.
|
|
||||||
/// </summary>
|
|
||||||
public struct TimeData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of time that has elapsed since the pending promise started running
|
|
||||||
/// </summary>
|
|
||||||
public float elapsedTime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The amount of time since the last time the pending promise was updated.
|
|
||||||
/// </summary>
|
|
||||||
public float deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPromiseTimer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the returned promise once the time has elapsed
|
|
||||||
/// </summary>
|
|
||||||
IPromise WaitFor(float seconds);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the returned promise once the predicate evaluates to true
|
|
||||||
/// </summary>
|
|
||||||
IPromise WaitUntil(Func<TimeData, bool> predicate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the returned promise once the predicate evaluates to false
|
|
||||||
/// </summary>
|
|
||||||
IPromise WaitWhile(Func<TimeData, bool> predicate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update all pending promises. Must be called for the promises to progress and resolve at all.
|
|
||||||
/// </summary>
|
|
||||||
void Update(float deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PromiseTimer : IPromiseTimer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The current running total for time that this PromiseTimer has run for
|
|
||||||
/// </summary>
|
|
||||||
private float curTime;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Currently pending promises
|
|
||||||
/// </summary>
|
|
||||||
private List<PredicateWait> waiting = new List<PredicateWait>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the returned promise once the time has elapsed
|
|
||||||
/// </summary>
|
|
||||||
public IPromise WaitFor(float seconds)
|
|
||||||
{
|
|
||||||
return WaitUntil(t => t.elapsedTime >= seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the returned promise once the predicate evaluates to false
|
|
||||||
/// </summary>
|
|
||||||
public IPromise WaitWhile(Func<TimeData, bool> predicate)
|
|
||||||
{
|
|
||||||
return WaitUntil(t => !predicate(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the returned promise once the predicate evalutes to true
|
|
||||||
/// </summary>
|
|
||||||
public IPromise WaitUntil(Func<TimeData, bool> predicate)
|
|
||||||
{
|
|
||||||
var promise = new Promise();
|
|
||||||
|
|
||||||
var wait = new PredicateWait()
|
|
||||||
{
|
|
||||||
timeStarted = curTime,
|
|
||||||
pendingPromise = promise,
|
|
||||||
timeData = new TimeData(),
|
|
||||||
predicate = predicate
|
|
||||||
};
|
|
||||||
|
|
||||||
waiting.Add(wait);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update all pending promises. Must be called for the promises to progress and resolve at all.
|
|
||||||
/// </summary>
|
|
||||||
public void Update(float deltaTime)
|
|
||||||
{
|
|
||||||
curTime += deltaTime;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < waiting.Count)
|
|
||||||
{
|
|
||||||
var wait = waiting[i];
|
|
||||||
|
|
||||||
var newElapsedTime = curTime - wait.timeStarted;
|
|
||||||
wait.timeData.deltaTime = newElapsedTime - wait.timeData.elapsedTime;
|
|
||||||
wait.timeData.elapsedTime = newElapsedTime;
|
|
||||||
|
|
||||||
bool result;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = wait.predicate(wait.timeData);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
wait.pendingPromise.Reject(ex);
|
|
||||||
waiting.RemoveAt(i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
wait.pendingPromise.Resolve();
|
|
||||||
waiting.RemoveAt(i);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: bab6d12600d36db4b81c0aa04f0fd685
|
|
||||||
timeCreated: 1479581013
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,930 +0,0 @@
|
||||||
using ZenFulcrum.EmbeddedBrowser.Promises;
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implements a non-generic C# promise, this is a promise that simply resolves without delivering a value.
|
|
||||||
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
||||||
///
|
|
||||||
/// This can also be waited on in a Unity coroutine.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPromise
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Set the name of the promise, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
IPromise WithName(string name);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// onRejected is called on error.
|
|
||||||
/// </summary>
|
|
||||||
void Done(Action onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
void Done(Action onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Complete the promise. Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
void Done();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle errors for the promise.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Catch(Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Then(Func<IPromise> onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Then(Action onResolved);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Then(Func<IPromise> onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// </summary>
|
|
||||||
IPromise Then(Action onResolved, Action<Exception> onRejected);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
IPromise ThenAll(Func<IEnumerable<IPromise>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// Converts to a non-value promise.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain a sequence of operations using promises.
|
|
||||||
/// Reutrn a collection of functions each of which starts an async operation and yields a promise.
|
|
||||||
/// Each function will be called and each promise resolved in turn.
|
|
||||||
/// The resulting promise is resolved after each promise is resolved in sequence.
|
|
||||||
/// </summary>
|
|
||||||
IPromise ThenSequence(Func<IEnumerable<Func<IPromise>>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// </summary>
|
|
||||||
IPromise ThenRace(Func<IEnumerable<IPromise>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Converts to a value promise.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// </summary>
|
|
||||||
IPromise<ConvertedT> ThenRace<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns an enumerable that yields null until the promise is settled.
|
|
||||||
/// ("To WaitFor" like the WaitForXXYY functions Unity provides.)
|
|
||||||
/// Suitable for use with a Unity coroutine's "yield return promise.ToWaitFor()"
|
|
||||||
///
|
|
||||||
/// If throwOnFail is true, the coroutine will abort on promise rejection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
IEnumerator ToWaitFor(bool abortOnFail = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for a promise that can be rejected or resolved.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPendingPromise : IRejectable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the promise with a particular value.
|
|
||||||
/// </summary>
|
|
||||||
void Resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Used to list information of pending promises.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPromiseInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Id of the promise.
|
|
||||||
/// </summary>
|
|
||||||
int Id { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Human-readable name for the promise.
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Arguments to the UnhandledError event.
|
|
||||||
/// </summary>
|
|
||||||
public class ExceptionEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
internal ExceptionEventArgs(Exception exception)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => exception);
|
|
||||||
|
|
||||||
this.Exception = exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Exception Exception
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
private set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a handler invoked when the promise is rejected.
|
|
||||||
/// </summary>
|
|
||||||
public struct RejectHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Callback fn.
|
|
||||||
/// </summary>
|
|
||||||
public Action<Exception> callback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The promise that is rejected when there is an error while invoking the handler.
|
|
||||||
/// </summary>
|
|
||||||
public IRejectable rejectable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implements a non-generic C# promise, this is a promise that simply resolves without delivering a value.
|
|
||||||
/// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
||||||
/// </summary>
|
|
||||||
public class Promise : IPromise, IPendingPromise, IPromiseInfo
|
|
||||||
{
|
|
||||||
static Promise()
|
|
||||||
{
|
|
||||||
UnhandledException += (sender, args) => {
|
|
||||||
UnityEngine.Debug.LogWarning("Rejection: " + args.Exception.Message + "\n" + args.Exception.StackTrace);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set to true to enable tracking of promises.
|
|
||||||
/// </summary>
|
|
||||||
public static bool EnablePromiseTracking = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event raised for unhandled errors.
|
|
||||||
/// For this to work you have to complete your promises with a call to Done().
|
|
||||||
/// </summary>
|
|
||||||
public static event EventHandler<ExceptionEventArgs> UnhandledException
|
|
||||||
{
|
|
||||||
add { unhandlerException += value; }
|
|
||||||
remove { unhandlerException -= value; }
|
|
||||||
}
|
|
||||||
private static EventHandler<ExceptionEventArgs> unhandlerException;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Id for the next promise that is created.
|
|
||||||
/// </summary>
|
|
||||||
internal static int nextPromiseId = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Information about pending promises.
|
|
||||||
/// </summary>
|
|
||||||
internal static HashSet<IPromiseInfo> pendingPromises = new HashSet<IPromiseInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Information about pending promises, useful for debugging.
|
|
||||||
/// This is only populated when 'EnablePromiseTracking' is set to true.
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerable<IPromiseInfo> GetPendingPromises()
|
|
||||||
{
|
|
||||||
return pendingPromises;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The exception when the promise is rejected.
|
|
||||||
/// </summary>
|
|
||||||
private Exception rejectionException;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Error handlers.
|
|
||||||
/// </summary>
|
|
||||||
private List<RejectHandler> rejectHandlers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a handler invoked when the promise is resolved.
|
|
||||||
/// </summary>
|
|
||||||
public struct ResolveHandler
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Callback fn.
|
|
||||||
/// </summary>
|
|
||||||
public Action callback;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The promise that is rejected when there is an error while invoking the handler.
|
|
||||||
/// </summary>
|
|
||||||
public IRejectable rejectable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completed handlers that accept no value.
|
|
||||||
/// </summary>
|
|
||||||
private List<ResolveHandler> resolveHandlers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// ID of the promise, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Name of the promise, when set, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tracks the current state of the promise.
|
|
||||||
/// </summary>
|
|
||||||
public PromiseState CurState { get; private set; }
|
|
||||||
|
|
||||||
public Promise()
|
|
||||||
{
|
|
||||||
this.CurState = PromiseState.Pending;
|
|
||||||
if (EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
pendingPromises.Add(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Promise(Action<Action, Action<Exception>> resolver)
|
|
||||||
{
|
|
||||||
this.CurState = PromiseState.Pending;
|
|
||||||
if (EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
pendingPromises.Add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
resolver(
|
|
||||||
// Resolve
|
|
||||||
() => Resolve(),
|
|
||||||
|
|
||||||
// Reject
|
|
||||||
ex => Reject(ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Reject(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a rejection handler for this promise.
|
|
||||||
/// </summary>
|
|
||||||
private void AddRejectHandler(Action<Exception> onRejected, IRejectable rejectable)
|
|
||||||
{
|
|
||||||
if (rejectHandlers == null)
|
|
||||||
{
|
|
||||||
rejectHandlers = new List<RejectHandler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectHandlers.Add(new RejectHandler()
|
|
||||||
{
|
|
||||||
callback = onRejected,
|
|
||||||
rejectable = rejectable
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolve handler for this promise.
|
|
||||||
/// </summary>
|
|
||||||
private void AddResolveHandler(Action onResolved, IRejectable rejectable)
|
|
||||||
{
|
|
||||||
if (resolveHandlers == null)
|
|
||||||
{
|
|
||||||
resolveHandlers = new List<ResolveHandler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveHandlers.Add(new ResolveHandler()
|
|
||||||
{
|
|
||||||
callback = onResolved,
|
|
||||||
rejectable = rejectable
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke a single error handler.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeRejectHandler(Action<Exception> callback, IRejectable rejectable, Exception value)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => callback);
|
|
||||||
// Argument.NotNull(() => rejectable);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
callback(value);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
rejectable.Reject(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke a single resolve handler.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeResolveHandler(Action callback, IRejectable rejectable)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => callback);
|
|
||||||
// Argument.NotNull(() => rejectable);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
rejectable.Reject(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper function clear out all handlers after resolution or rejection.
|
|
||||||
/// </summary>
|
|
||||||
private void ClearHandlers()
|
|
||||||
{
|
|
||||||
rejectHandlers = null;
|
|
||||||
resolveHandlers = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke all reject handlers.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeRejectHandlers(Exception ex)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => ex);
|
|
||||||
|
|
||||||
if (rejectHandlers != null)
|
|
||||||
{
|
|
||||||
rejectHandlers.Each(handler => InvokeRejectHandler(handler.callback, handler.rejectable, ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoke all resolve handlers.
|
|
||||||
/// </summary>
|
|
||||||
private void InvokeResolveHandlers()
|
|
||||||
{
|
|
||||||
if (resolveHandlers != null)
|
|
||||||
{
|
|
||||||
resolveHandlers.Each(handler => InvokeResolveHandler(handler.callback, handler.rejectable));
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reject the promise with an exception.
|
|
||||||
/// </summary>
|
|
||||||
public void Reject(Exception ex)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => ex);
|
|
||||||
|
|
||||||
if (CurState != PromiseState.Pending)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Attempt to reject a promise that is already in state: " + CurState + ", a promise can only be rejected when it is still in state: " + PromiseState.Pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
rejectionException = ex;
|
|
||||||
CurState = PromiseState.Rejected;
|
|
||||||
|
|
||||||
if (EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
pendingPromises.Remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeRejectHandlers(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolve the promise with a particular value.
|
|
||||||
/// </summary>
|
|
||||||
public void Resolve()
|
|
||||||
{
|
|
||||||
if (CurState != PromiseState.Pending)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("Attempt to resolve a promise that is already in state: " + CurState + ", a promise can only be resolved when it is still in state: " + PromiseState.Pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
CurState = PromiseState.Resolved;
|
|
||||||
|
|
||||||
if (EnablePromiseTracking)
|
|
||||||
{
|
|
||||||
pendingPromises.Remove(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeResolveHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// onRejected is called on error.
|
|
||||||
/// </summary>
|
|
||||||
public void Done(Action onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
Then(onResolved, onRejected)
|
|
||||||
.Catch(ex =>
|
|
||||||
Promise.PropagateUnhandledException(this, ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Completes the promise.
|
|
||||||
/// onResolved is called on successful completion.
|
|
||||||
/// Adds a default error handler.
|
|
||||||
/// </summary>
|
|
||||||
public void Done(Action onResolved)
|
|
||||||
{
|
|
||||||
Then(onResolved)
|
|
||||||
.Catch(ex =>
|
|
||||||
Promise.PropagateUnhandledException(this, ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Complete the promise. Adds a defualt error handler.
|
|
||||||
/// </summary>
|
|
||||||
public void Done()
|
|
||||||
{
|
|
||||||
Catch(ex =>
|
|
||||||
Promise.PropagateUnhandledException(this, ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the name of the promise, useful for debugging.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise WithName(string name)
|
|
||||||
{
|
|
||||||
this.Name = name;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle errors for the promise.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Catch(Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => onRejected);
|
|
||||||
|
|
||||||
var resultPromise = new Promise();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action resolveHandler = () =>
|
|
||||||
{
|
|
||||||
resultPromise.Resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved)
|
|
||||||
{
|
|
||||||
return Then(onResolved, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback that chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Then(Func<IPromise> onResolved)
|
|
||||||
{
|
|
||||||
return Then(onResolved, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Then(Action onResolved)
|
|
||||||
{
|
|
||||||
return Then(onResolved, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a value promise (optionally converting to a different value type).
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> Then<ConvertedT>(Func<IPromise<ConvertedT>> onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
// This version of the function must supply an onResolved.
|
|
||||||
// Otherwise there is now way to get the converted value to pass to the resulting promise.
|
|
||||||
// Argument.NotNull(() => onResolved);
|
|
||||||
|
|
||||||
var resultPromise = new Promise<ConvertedT>();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action resolveHandler = () =>
|
|
||||||
{
|
|
||||||
onResolved()
|
|
||||||
.Then(
|
|
||||||
// Should not be necessary to specify the arg type on the next line, but Unity (mono) has an internal compiler error otherwise.
|
|
||||||
(ConvertedT chainedValue) => resultPromise.Resolve(chainedValue),
|
|
||||||
ex => resultPromise.Reject(ex)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
if (onRejected != null)
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// The resolved callback chains a non-value promise.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Then(Func<IPromise> onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
var resultPromise = new Promise();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action resolveHandler = () =>
|
|
||||||
{
|
|
||||||
if (onResolved != null)
|
|
||||||
{
|
|
||||||
onResolved()
|
|
||||||
.Then(
|
|
||||||
() => resultPromise.Resolve(),
|
|
||||||
ex => resultPromise.Reject(ex)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
resultPromise.Resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
if (onRejected != null)
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a resolved callback and a rejected callback.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise Then(Action onResolved, Action<Exception> onRejected)
|
|
||||||
{
|
|
||||||
var resultPromise = new Promise();
|
|
||||||
resultPromise.WithName(Name);
|
|
||||||
|
|
||||||
Action resolveHandler = () =>
|
|
||||||
{
|
|
||||||
if (onResolved != null)
|
|
||||||
{
|
|
||||||
onResolved();
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
Action<Exception> rejectHandler = ex =>
|
|
||||||
{
|
|
||||||
if (onRejected != null)
|
|
||||||
{
|
|
||||||
onRejected(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
};
|
|
||||||
|
|
||||||
ActionHandlers(resultPromise, resolveHandler, rejectHandler);
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Helper function to invoke or register resolve/reject handlers.
|
|
||||||
/// </summary>
|
|
||||||
private void ActionHandlers(IRejectable resultPromise, Action resolveHandler, Action<Exception> rejectHandler)
|
|
||||||
{
|
|
||||||
if (CurState == PromiseState.Resolved)
|
|
||||||
{
|
|
||||||
InvokeResolveHandler(resolveHandler, resultPromise);
|
|
||||||
}
|
|
||||||
else if (CurState == PromiseState.Rejected)
|
|
||||||
{
|
|
||||||
InvokeRejectHandler(rejectHandler, resultPromise, rejectionException);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddResolveHandler(resolveHandler, resultPromise);
|
|
||||||
AddRejectHandler(rejectHandler, resultPromise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise ThenAll(Func<IEnumerable<IPromise>> chain)
|
|
||||||
{
|
|
||||||
return Then(() => Promise.All(chain()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain an enumerable of promises, all of which must resolve.
|
|
||||||
/// Converts to a non-value promise.
|
|
||||||
/// The resulting promise is resolved when all of the promises have resolved.
|
|
||||||
/// It is rejected as soon as any of the promises have been rejected.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<IEnumerable<ConvertedT>> ThenAll<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain)
|
|
||||||
{
|
|
||||||
return Then(() => Promise<ConvertedT>.All(chain()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when all of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns a promise of a collection of the resolved results.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise All(params IPromise[] promises)
|
|
||||||
{
|
|
||||||
return All((IEnumerable<IPromise>)promises); // Cast is required to force use of the other All function.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when all of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns a promise of a collection of the resolved results.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise All(IEnumerable<IPromise> promises)
|
|
||||||
{
|
|
||||||
var promisesArray = promises.ToArray();
|
|
||||||
if (promisesArray.Length == 0)
|
|
||||||
{
|
|
||||||
return Promise.Resolved();
|
|
||||||
}
|
|
||||||
|
|
||||||
var remainingCount = promisesArray.Length;
|
|
||||||
var resultPromise = new Promise();
|
|
||||||
resultPromise.WithName("All");
|
|
||||||
|
|
||||||
promisesArray.Each((promise, index) =>
|
|
||||||
{
|
|
||||||
promise
|
|
||||||
.Catch(ex =>
|
|
||||||
{
|
|
||||||
if (resultPromise.CurState == PromiseState.Pending)
|
|
||||||
{
|
|
||||||
// If a promise errorred and the result promise is still pending, reject it.
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Then(() =>
|
|
||||||
{
|
|
||||||
--remainingCount;
|
|
||||||
if (remainingCount <= 0)
|
|
||||||
{
|
|
||||||
// This will never happen if any of the promises errorred.
|
|
||||||
resultPromise.Resolve();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Done();
|
|
||||||
});
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain a sequence of operations using promises.
|
|
||||||
/// Reutrn a collection of functions each of which starts an async operation and yields a promise.
|
|
||||||
/// Each function will be called and each promise resolved in turn.
|
|
||||||
/// The resulting promise is resolved after each promise is resolved in sequence.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise ThenSequence(Func<IEnumerable<Func<IPromise>>> chain)
|
|
||||||
{
|
|
||||||
return Then(() => Sequence(chain()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain a number of operations using promises.
|
|
||||||
/// Takes a number of functions each of which starts an async operation and yields a promise.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise Sequence(params Func<IPromise>[] fns)
|
|
||||||
{
|
|
||||||
return Sequence((IEnumerable<Func<IPromise>>)fns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Chain a sequence of operations using promises.
|
|
||||||
/// Takes a collection of functions each of which starts an async operation and yields a promise.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise Sequence(IEnumerable<Func<IPromise>> fns)
|
|
||||||
{
|
|
||||||
return fns.Aggregate(
|
|
||||||
Promise.Resolved(),
|
|
||||||
(prevPromise, fn) =>
|
|
||||||
{
|
|
||||||
return prevPromise.Then(() => fn());
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise ThenRace(Func<IEnumerable<IPromise>> chain)
|
|
||||||
{
|
|
||||||
return Then(() => Promise.Race(chain()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Takes a function that yields an enumerable of promises.
|
|
||||||
/// Converts to a value promise.
|
|
||||||
/// Returns a promise that resolves when the first of the promises has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public IPromise<ConvertedT> ThenRace<ConvertedT>(Func<IEnumerable<IPromise<ConvertedT>>> chain)
|
|
||||||
{
|
|
||||||
return Then(() => Promise<ConvertedT>.Race(chain()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise Race(params IPromise[] promises)
|
|
||||||
{
|
|
||||||
return Race((IEnumerable<IPromise>)promises); // Cast is required to force use of the other function.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a promise that resolves when the first of the promises in the enumerable argument have resolved.
|
|
||||||
/// Returns the value from the first promise that has resolved.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise Race(IEnumerable<IPromise> promises)
|
|
||||||
{
|
|
||||||
var promisesArray = promises.ToArray();
|
|
||||||
if (promisesArray.Length == 0)
|
|
||||||
{
|
|
||||||
throw new ApplicationException("At least 1 input promise must be provided for Race");
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultPromise = new Promise();
|
|
||||||
resultPromise.WithName("Race");
|
|
||||||
|
|
||||||
promisesArray.Each((promise, index) =>
|
|
||||||
{
|
|
||||||
promise
|
|
||||||
.Catch(ex =>
|
|
||||||
{
|
|
||||||
if (resultPromise.CurState == PromiseState.Pending)
|
|
||||||
{
|
|
||||||
// If a promise errorred and the result promise is still pending, reject it.
|
|
||||||
resultPromise.Reject(ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Then(() =>
|
|
||||||
{
|
|
||||||
if (resultPromise.CurState == PromiseState.Pending)
|
|
||||||
{
|
|
||||||
resultPromise.Resolve();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.Done();
|
|
||||||
});
|
|
||||||
|
|
||||||
return resultPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a simple value directly into a resolved promise.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise Resolved()
|
|
||||||
{
|
|
||||||
var promise = new Promise();
|
|
||||||
promise.Resolve();
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert an exception directly into a rejected promise.
|
|
||||||
/// </summary>
|
|
||||||
public static IPromise Rejected(Exception ex)
|
|
||||||
{
|
|
||||||
// Argument.NotNull(() => ex);
|
|
||||||
|
|
||||||
var promise = new Promise();
|
|
||||||
promise.Reject(ex);
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Raises the UnhandledException event.
|
|
||||||
/// </summary>
|
|
||||||
internal static void PropagateUnhandledException(object sender, Exception ex)
|
|
||||||
{
|
|
||||||
if (unhandlerException != null)
|
|
||||||
{
|
|
||||||
unhandlerException(sender, new ExceptionEventArgs(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Enumerated : IEnumerator {
|
|
||||||
private Promise promise;
|
|
||||||
private bool abortOnFail;
|
|
||||||
|
|
||||||
public Enumerated(Promise promise, bool abortOnFail) {
|
|
||||||
this.promise = promise;
|
|
||||||
this.abortOnFail = abortOnFail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool MoveNext() {
|
|
||||||
if (abortOnFail && promise.CurState == PromiseState.Rejected) {
|
|
||||||
throw promise.rejectionException;
|
|
||||||
}
|
|
||||||
return promise.CurState == PromiseState.Pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset() { }
|
|
||||||
|
|
||||||
public object Current { get { return null; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IEnumerator ToWaitFor(bool abortOnFail = false) {
|
|
||||||
var ret = new Enumerated(this, abortOnFail);
|
|
||||||
//someone will poll for completion, so act like we've been terminated
|
|
||||||
Done(() => {}, ex => {});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 8c0ade0b4d4a8704baa1b214a700ed50
|
|
||||||
timeCreated: 1479581013
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,22 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hooks to ensure that zfb gets shut down on app (or playmode) exit.
|
|
||||||
* (This used to be used only for builds, but now that we can cleanly shut down the browser
|
|
||||||
* system after every playmode run it's used in the Editor too.)
|
|
||||||
*/
|
|
||||||
class StandaloneShutdown : MonoBehaviour {
|
|
||||||
public static void Create() {
|
|
||||||
var go = new GameObject("ZFB Shutdown");
|
|
||||||
go.AddComponent<StandaloneShutdown>();
|
|
||||||
DontDestroyOnLoad(go);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnApplicationQuit() {
|
|
||||||
BrowserNative.UnloadNative();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 88efef500fce2a24b96f9f6a661a15fb
|
|
||||||
timeCreated: 1449682818
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,151 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Compression;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using UnityEngine;
|
|
||||||
#if UNITY_2017_3_OR_NEWER
|
|
||||||
using UnityEngine.Networking;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements fetching BrowserAssets for standalone builds.
|
|
||||||
*
|
|
||||||
* During build, everything in BrowserAssets is packaged into a single file with the following format:
|
|
||||||
* brString {FileHeader}
|
|
||||||
* i32 numEntries
|
|
||||||
* {numEntries} IndexEntry objects
|
|
||||||
* data //The data section is a series of filedata chunks, as laid out in the index.
|
|
||||||
*/
|
|
||||||
public class StandaloneWebResources : WebResources {
|
|
||||||
public struct IndexEntry {
|
|
||||||
public string name;
|
|
||||||
public long offset;
|
|
||||||
public int length;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const string FileHeader = "zfbRes_v1";
|
|
||||||
|
|
||||||
protected Dictionary<string, IndexEntry> toc = new Dictionary<string, IndexEntry>();
|
|
||||||
protected string dataFile;
|
|
||||||
|
|
||||||
public StandaloneWebResources(string dataFile) {
|
|
||||||
this.dataFile = dataFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public const string DefaultPath = "Resources/browser_assets";
|
|
||||||
|
|
||||||
public void LoadIndex() {
|
|
||||||
using (var data = new BinaryReader(File.OpenRead(dataFile))) {
|
|
||||||
var header = data.ReadString();
|
|
||||||
if (header != FileHeader) throw new Exception("Invalid web resource file");
|
|
||||||
|
|
||||||
var num = data.ReadInt32();
|
|
||||||
|
|
||||||
for (int i = 0; i < num; ++i) {
|
|
||||||
var entry = new IndexEntry() {
|
|
||||||
name = data.ReadString(),
|
|
||||||
offset = data.ReadInt64(),
|
|
||||||
length = data.ReadInt32(),
|
|
||||||
};
|
|
||||||
toc[entry.name] = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void HandleRequest(int id, string url) {
|
|
||||||
var parsedURL = new Uri(url);
|
|
||||||
|
|
||||||
#if UNITY_2017_3_OR_NEWER
|
|
||||||
var path = UnityWebRequest.UnEscapeURL(parsedURL.AbsolutePath);
|
|
||||||
#else
|
|
||||||
var path = WWW.UnEscapeURL(parsedURL.AbsolutePath);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
IndexEntry entry;
|
|
||||||
if (!toc.TryGetValue(path, out entry)) {
|
|
||||||
SendError(id, "Not found", 404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new Thread(() => {
|
|
||||||
try {
|
|
||||||
var ext = Path.GetExtension(entry.name);
|
|
||||||
if (ext.Length > 0) ext = ext.Substring(1);
|
|
||||||
|
|
||||||
string mimeType;
|
|
||||||
if (!extensionMimeTypes.TryGetValue(ext, out mimeType)) {
|
|
||||||
mimeType = extensionMimeTypes["*"];
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var file = File.OpenRead(dataFile)) {
|
|
||||||
var pre = new ResponsePreamble {
|
|
||||||
headers = null,
|
|
||||||
length = entry.length,
|
|
||||||
mimeType = mimeType,
|
|
||||||
statusCode = 200,
|
|
||||||
};
|
|
||||||
SendPreamble(id, pre);
|
|
||||||
|
|
||||||
file.Seek(entry.offset, SeekOrigin.Begin);
|
|
||||||
var data = new byte[entry.length];
|
|
||||||
var readLen = file.Read(data, 0, entry.length);
|
|
||||||
if (readLen != data.Length) throw new Exception("Insufficient data for file");
|
|
||||||
|
|
||||||
SendData(id, data);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Debug.LogException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
}).Start();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteData(Dictionary<string, byte[]> files) {
|
|
||||||
var entries = new Dictionary<string, IndexEntry>();
|
|
||||||
|
|
||||||
using (var file = File.OpenWrite(dataFile)) {
|
|
||||||
var writer = new BinaryWriter(file, Encoding.UTF8 /*, true (Mono too old)*/);
|
|
||||||
writer.Write(FileHeader);
|
|
||||||
writer.Write(files.Count);
|
|
||||||
|
|
||||||
var tocStart = file.Position;
|
|
||||||
|
|
||||||
foreach (var kvp in files) {
|
|
||||||
writer.Write(kvp.Key);
|
|
||||||
writer.Write(0L);
|
|
||||||
writer.Write(0);
|
|
||||||
}
|
|
||||||
//we'll come back and fill it in right later
|
|
||||||
|
|
||||||
foreach (var kvp in files) {
|
|
||||||
var data = kvp.Value;
|
|
||||||
var entry = new IndexEntry {
|
|
||||||
name = kvp.Key,
|
|
||||||
length = kvp.Value.Length,
|
|
||||||
offset = file.Position,
|
|
||||||
};
|
|
||||||
|
|
||||||
writer.Write(data);
|
|
||||||
entries[kvp.Key] = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
//now go back and write the correct data.
|
|
||||||
writer.Seek((int)tocStart, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
foreach (var kvp in files) {
|
|
||||||
var entry = entries[kvp.Key];
|
|
||||||
writer.Write(kvp.Key);
|
|
||||||
writer.Write(entry.offset);
|
|
||||||
writer.Write(entry.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 116a67bb616e372428beb4db63fa0591
|
|
||||||
timeCreated: 1449617902
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,64 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cooks up the user agent to use for the browser.
|
|
||||||
*
|
|
||||||
* Ideally, we'd just say a little bit and let websites feature-detect, but many websites (sadly)
|
|
||||||
* still use UA sniffing to decide what version of the page to give us.
|
|
||||||
*
|
|
||||||
* Notably, **Google** does this (for example, maps.google.com), which I find rather strange considering
|
|
||||||
* that I'd expect them to be among those telling us to feature-detect.
|
|
||||||
*
|
|
||||||
* So, use this class to generate a pile of turd that looks like every other browser out there acting like
|
|
||||||
* every browser that came before it so we get the "real" version of pages when we browse.
|
|
||||||
*/
|
|
||||||
public class UserAgent {
|
|
||||||
|
|
||||||
private static string agentOverride;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a user agent that, hopefully, tricks legacy, stupid, non-feature-detection websites
|
|
||||||
* into giving us their actual content.
|
|
||||||
*
|
|
||||||
* If you change this, the Editor will usually need to be restarted for changes to take effect.
|
|
||||||
*/
|
|
||||||
public static string GetUserAgent() {
|
|
||||||
if (agentOverride != null) return agentOverride;
|
|
||||||
|
|
||||||
var chromeVersion = Marshal.PtrToStringAnsi(BrowserNative.zfb_getVersion());
|
|
||||||
|
|
||||||
//(Note: I don't care what version of the OS you have, we're telling the website you have this
|
|
||||||
//version so you get a good site.)
|
|
||||||
string osStr = "Windows NT 6.1";
|
|
||||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
|
||||||
osStr = "Macintosh; Intel Mac OS X 10_7_0";
|
|
||||||
#elif UNITY_EDITOR_LINUX || UNITY_STANDALONE_LINUX
|
|
||||||
osStr = "X11; Linux x86_64";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var userAgent =
|
|
||||||
"Mozilla/5.0 " +
|
|
||||||
"(" + osStr + "; Unity 3D; ZFBrowser 2.1.0; " + Application.productName + " " + Application.version + ") " +
|
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + chromeVersion + " Safari/537.36"
|
|
||||||
;
|
|
||||||
|
|
||||||
//Chromium has issues with non-ASCII user agents.
|
|
||||||
userAgent = Regex.Replace(userAgent, @"[^\u0020-\u007E]", "?");
|
|
||||||
|
|
||||||
return userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetUserAgent(string userAgent) {
|
|
||||||
if (BrowserNative.NativeLoaded) {
|
|
||||||
throw new InvalidOperationException("User Agent can only be changed before native backend is initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
agentOverride = userAgent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: fd7ecd3989cd6dd4e8cf983f4829d97f
|
|
||||||
timeCreated: 1456246764
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,63 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser {
|
|
||||||
|
|
||||||
public static class Util {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sometimes creating a culture in a different thread causes Mono to crash
|
|
||||||
* with mono_class_vtable_full.
|
|
||||||
*
|
|
||||||
* This variant of StartsWith won't try to use a culture.
|
|
||||||
*/
|
|
||||||
public static bool SafeStartsWith(this string check, string starter) {
|
|
||||||
if (check == null || starter == null) return false;
|
|
||||||
|
|
||||||
if (check.Length < starter.Length) return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < starter.Length; ++i) {
|
|
||||||
if (check[i] != starter[i]) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a UTF8-encoded null-terminated string to a CLR string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="strIn"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string PtrToStringUTF8(IntPtr strIn) {
|
|
||||||
if (strIn == IntPtr.Zero) return null;
|
|
||||||
int strLen = 0;
|
|
||||||
while (Marshal.ReadByte(strIn, strLen) != 0) ++strLen;
|
|
||||||
var buffer = new byte[strLen];
|
|
||||||
Marshal.Copy(strIn, buffer, 0, strLen);
|
|
||||||
return Encoding.UTF8.GetString(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JSException : Exception {
|
|
||||||
public JSException(string what) : base(what) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum KeyAction {
|
|
||||||
Press, Release, PressAndRelease
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BrowserFocusState {
|
|
||||||
public bool hasKeyboardFocus;
|
|
||||||
public bool hasMouseFocus;
|
|
||||||
|
|
||||||
public string focusedTagName;
|
|
||||||
public bool focusedNodeEditable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BrowserNavState {
|
|
||||||
public bool canGoForward, canGoBack, loading;
|
|
||||||
public string url = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 6b8101d35cb5dd149bd00dc81dff0262
|
|
||||||
timeCreated: 1454553765
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,10 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 700b927cab691a449bd94222d6724dab
|
|
||||||
folderAsset: yes
|
|
||||||
timeCreated: 1510959402
|
|
||||||
licenseType: Store
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
|
@ -1,279 +0,0 @@
|
||||||
//#define OCULUS_SDK //<-- define this in your project settings if you have the OVR API
|
|
||||||
#if UNITY_2017_2_OR_NEWER
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.XR;
|
|
||||||
using ZenFulcrum.VR.OpenVRBinding;
|
|
||||||
|
|
||||||
|
|
||||||
namespace ZenFulcrum.EmbeddedBrowser.VR {
|
|
||||||
|
|
||||||
public enum InputAxis {
|
|
||||||
MainTrigger,//main index finger trigger
|
|
||||||
Grip,//Vive squeeze/Oculus hand trigger
|
|
||||||
JoypadX, JoypadY,//touchpad/joystick x/y position
|
|
||||||
Joypad,//touchpad/joystick click/press
|
|
||||||
Application,//application meanu/start button
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum JoyPadType {
|
|
||||||
None,
|
|
||||||
Joystick,
|
|
||||||
TouchPad,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unity's VR input system is sorely lacking in usefulness.
|
|
||||||
/// We can finally get pose data in a more-or-less straightforward manner (InputTracking.GetNodeStates,
|
|
||||||
/// state.TryGetPosition, etc.), but getting the axis and buttons is an awful mess.
|
|
||||||
///
|
|
||||||
/// This page https://docs.unity3d.com/Manual/OpenVRControllers.html says we can access the inputs via
|
|
||||||
/// the input system! And, you can look at (of all things) the Joystick names to see which joystick
|
|
||||||
/// is a VR controller at runtime. But guess what? There's no Unity API to fetch the value of a given
|
|
||||||
/// axis on a given controller! Extra-stupid, right? You can define a specific input in the input menu,
|
|
||||||
/// but that doesn't help when you switch to a different computer and the joystick numbers change!
|
|
||||||
/// Short of defining an "input" for every possible controller * every axis you use, there's not a
|
|
||||||
/// way to get the controller axis inputs in a way that will work reliably across different machines.
|
|
||||||
///
|
|
||||||
/// (We can fetch buttons manually via Input.GetKey(KeyCode.Joystick1Button0 + buttonId + joystickIdx * 20),
|
|
||||||
/// but this don't address the axis issue at all.)
|
|
||||||
///
|
|
||||||
/// Anyway, this is a workaround. Around another Unity problem. Implemented by hooking into backend APIs directly.
|
|
||||||
/// </summary>
|
|
||||||
public class VRInput {
|
|
||||||
private static VRInput impl;
|
|
||||||
|
|
||||||
public static void Init() {
|
|
||||||
if (impl == null) impl = GetImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the state of the given button/axis.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="node"></param>
|
|
||||||
/// <param name="axis"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static float GetAxis(XRNodeState node, InputAxis axis) {
|
|
||||||
if (impl == null) impl = GetImpl();
|
|
||||||
return impl.GetAxisValue(node, axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the controller is capable, returns if (and sometimes how closely) the player is touching
|
|
||||||
/// the given control.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="node"></param>
|
|
||||||
/// <param name="axis"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static float GetTouch(XRNodeState node, InputAxis axis) {
|
|
||||||
if (impl == null) impl = GetImpl();
|
|
||||||
return impl.GetTouchValue(node, axis);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual float GetAxisValue(XRNodeState node, InputAxis axis) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual float GetTouchValue(XRNodeState node, InputAxis axis) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<ulong, JoyPadType> nodeTypes = new Dictionary<ulong, JoyPadType>();
|
|
||||||
public static JoyPadType GetJoypadType(XRNodeState node) {
|
|
||||||
JoyPadType ret;
|
|
||||||
if (!nodeTypes.TryGetValue(node.uniqueID, out ret)) {
|
|
||||||
ret = JoyPadType.None;
|
|
||||||
|
|
||||||
if (impl == null) impl = GetImpl();
|
|
||||||
var name = impl.GetNodeName(node);
|
|
||||||
|
|
||||||
if (name.Contains("Oculus Touch Controller") || name.StartsWith("Oculus Rift CV1")) {
|
|
||||||
//OpenVR gives us "Oculus Rift CV1 (Left Controller)" etc. where I wish it would mention the type of controller (Touch)
|
|
||||||
ret = JoyPadType.Joystick;
|
|
||||||
} else if (name.StartsWith("Vive Controller")) {
|
|
||||||
ret = JoyPadType.TouchPad;
|
|
||||||
} else {
|
|
||||||
Debug.LogWarning("Unknown controller type: " + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeTypes[node.uniqueID] = ret;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual string GetNodeName(XRNodeState node) {
|
|
||||||
return InputTracking.GetNodeName(node.uniqueID);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// public virtual JoyPadType JoypadTypeValue(XRNodeState node) { return JoyPadType.None; }
|
|
||||||
|
|
||||||
private static VRInput GetImpl() {
|
|
||||||
if (XRSettings.loadedDeviceName == "OpenVR") {
|
|
||||||
return new OpenVRInput();
|
|
||||||
} else if (XRSettings.loadedDeviceName == "Oculus") {
|
|
||||||
#if OCULUS_SDK
|
|
||||||
return new OculusVRInput();
|
|
||||||
#else
|
|
||||||
Debug.LogError("To use the Oculus API for input, import the Oculus SDK and define OCULUS_SDK");
|
|
||||||
return new VRInput();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Debug.LogError("Unknown VR input system: " + XRSettings.loadedDeviceName);
|
|
||||||
return new VRInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class OpenVRInput : VRInput {
|
|
||||||
|
|
||||||
protected VRControllerState_t lastState;
|
|
||||||
|
|
||||||
public static string GetStringProperty(uint deviceId, ETrackedDeviceProperty prop) {
|
|
||||||
var buffer = new StringBuilder((int)OpenVR.k_unMaxPropertyStringSize);
|
|
||||||
ETrackedPropertyError err = ETrackedPropertyError.TrackedProp_Success;
|
|
||||||
|
|
||||||
OpenVR.System.GetStringTrackedDeviceProperty(
|
|
||||||
deviceId, prop,
|
|
||||||
buffer, OpenVR.k_unMaxPropertyStringSize, ref err
|
|
||||||
);
|
|
||||||
|
|
||||||
if (err != ETrackedPropertyError.TrackedProp_Success) {
|
|
||||||
throw new Exception("Failed to get property " + prop + " on device " + deviceId + ": " + err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetNodeName(XRNodeState node) {
|
|
||||||
var deviceId = (uint)GetDeviceId(node);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return GetStringProperty(deviceId, ETrackedDeviceProperty.Prop_ModelNumber_String);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Debug.LogError("Failed to get device name for device " + deviceId + ": " + ex.Message);
|
|
||||||
return base.GetNodeName(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void ReadState(XRNodeState node) {
|
|
||||||
if (OpenVR.System == null) {
|
|
||||||
Debug.LogWarning("OpenVR not active");
|
|
||||||
lastState = default(VRControllerState_t);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var controllerId = GetDeviceId(node);
|
|
||||||
if (controllerId < 0) {
|
|
||||||
lastState = default(VRControllerState_t);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Debug.Log("Id is " + controllerId);
|
|
||||||
|
|
||||||
var res = OpenVR.System.GetControllerState(
|
|
||||||
(uint)controllerId, ref lastState,
|
|
||||||
(uint)System.Runtime.InteropServices.Marshal.SizeOf(typeof(VRControllerState_t))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
Debug.LogWarning("Failed to get controller state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float GetAxisValue(XRNodeState node, InputAxis axis) {
|
|
||||||
ReadState(node);
|
|
||||||
|
|
||||||
switch (axis) {
|
|
||||||
case InputAxis.MainTrigger:
|
|
||||||
return lastState.rAxis1.x;
|
|
||||||
case InputAxis.Grip:
|
|
||||||
return (lastState.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_Grip)) != 0 ? 1 : 0;
|
|
||||||
case InputAxis.JoypadX:
|
|
||||||
return lastState.rAxis0.x;
|
|
||||||
case InputAxis.JoypadY:
|
|
||||||
return lastState.rAxis0.y;
|
|
||||||
case InputAxis.Joypad:
|
|
||||||
return (lastState.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad)) != 0 ? 1 : 0;
|
|
||||||
case InputAxis.Application:
|
|
||||||
return (lastState.ulButtonPressed & (1ul << (int)EVRButtonId.k_EButton_ApplicationMenu)) != 0 ? 1 : 0;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException("axis", axis, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float GetTouchValue(XRNodeState node, InputAxis axis) {
|
|
||||||
ReadState(node);
|
|
||||||
|
|
||||||
switch (axis) {
|
|
||||||
case InputAxis.Joypad:
|
|
||||||
return (lastState.ulButtonTouched & (1ul << (int)EVRButtonId.k_EButton_SteamVR_Touchpad)) != 0 ? 1 : 0;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetDeviceId(XRNodeState node) {
|
|
||||||
var targetRole = node.nodeType == XRNode.LeftHand ? ETrackedControllerRole.LeftHand : ETrackedControllerRole.RightHand;
|
|
||||||
for (uint i = 0; i < OpenVR.k_unMaxTrackedDeviceCount; i++) {
|
|
||||||
var role = OpenVR.System.GetControllerRoleForTrackedDeviceIndex(i);
|
|
||||||
if (role == targetRole) return (int)i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if OCULUS_SDK
|
|
||||||
class OculusVRInput : VRInput {
|
|
||||||
|
|
||||||
protected override float GetAxisValue(XRNodeState node, InputAxis axis) {
|
|
||||||
var controller = GetController(node);
|
|
||||||
OVRPlugin.ControllerState4 state = OVRPlugin.GetControllerState4((uint)controller);
|
|
||||||
|
|
||||||
switch (axis) {
|
|
||||||
case InputAxis.MainTrigger:
|
|
||||||
return controller == OVRInput.Controller.LTouch ? state.LIndexTrigger : state.RIndexTrigger;
|
|
||||||
case InputAxis.Grip:
|
|
||||||
return controller == OVRInput.Controller.LTouch ? state.LHandTrigger : state.RHandTrigger;
|
|
||||||
case InputAxis.JoypadX:
|
|
||||||
case InputAxis.JoypadY: {
|
|
||||||
var joy = controller == OVRInput.Controller.LTouch ? state.LThumbstick : state.RThumbstick;
|
|
||||||
return axis == InputAxis.JoypadX ? joy.x : joy.y;
|
|
||||||
}
|
|
||||||
case InputAxis.Joypad: {
|
|
||||||
var buttonId = controller == OVRInput.Controller.LTouch ? 0x00000400 : 0x00000004; //see enum ovrButton_ in OVER_CAPI.h
|
|
||||||
return (state.Buttons & buttonId) != 0 ? 1 : 0;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OVRInput.Controller GetController(XRNodeState node) {
|
|
||||||
switch (node.nodeType) {
|
|
||||||
case XRNode.LeftHand:
|
|
||||||
return OVRInput.Controller.LTouch;
|
|
||||||
case XRNode.RightHand:
|
|
||||||
return OVRInput.Controller.RTouch;
|
|
||||||
default:
|
|
||||||
return OVRInput.Controller.None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override float GetTouchValue(XRNodeState node, InputAxis axis) {
|
|
||||||
//nothing touch-related is presently used for Oculus Touch controllers, so nothing is all we need here
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,13 +0,0 @@
|
||||||
fileFormatVersion: 2
|
|
||||||
guid: 9a740e9fb5a3b4f49b1074b056b4f38d
|
|
||||||
timeCreated: 1510961690
|
|
||||||
licenseType: Store
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue