123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- 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; }
- }
- }
|