context: /*
   * @author Valentin Simonov / http://va.lent.in/
   */
  
  using System;
  using TouchScript.Pointers;
  using TouchScript.Utils;
  using UnityEngine;
  
  namespace TouchScript.InputSources.InputHandlers
  {
      /// <summary>
      /// Unity mouse handling implementation which can be embedded and controlled from other (input) classes.
      /// </summary>
      public class MouseHandler : IInputSource, IDisposable
      {
          #region Consts
  
          private enum State
          {
              /// <summary>
              /// Only mouse pointer is active
              /// </summary>
              Mouse,
  
              /// <summary>
              /// ALT is pressed but mouse isn't
              /// </summary>
              WaitingForFake,
  
              /// <summary>
              /// Mouse and fake pointers are moving together after ALT+PRESS
              /// </summary>
              MouseAndFake,
  
              /// <summary>
              /// After ALT+RELEASE fake pointer is stationary while mouse can move freely
              /// </summary>
              StationaryFake
          }
  
          #endregion
  
          #region Public properties
  
          /// <inheritdoc />
          public ICoordinatesRemapper CoordinatesRemapper { get; set; }
  
          /// <summary>
          /// Gets or sets a value indicating whether second pointer emulation using ALT+CLICK should be enabled.
          /// </summary>
          /// <value>
          /// <c>true</c> if second pointer emulation is enabled; otherwise, <c>false</c>.
          /// </value>
          public bool EmulateSecondMousePointer
          {
              get { return emulateSecondMousePointer; }
              set
              {
                  emulateSecondMousePointer = value;
                  if (fakeMousePointer != null) CancelPointer(fakeMousePointer, false);
              }
          }
  
          #endregion
  
          #region Private variables
  
          private bool emulateSecondMousePointer = true;
  
          private PointerDelegate addPointer;
          private PointerDelegate updatePointer;
          private PointerDelegate pressPointer;
          private PointerDelegate releasePointer;
          private PointerDelegate removePointer;
          private PointerDelegate cancelPointer;
  
          private State state;
          private ObjectPool<MousePointer> mousePool;
          private MousePointer mousePointer, fakeMousePointer;
          private Vector3 mousePointPos = Vector3.zero;
  
          #endregion
  
          /// <summary>
          /// Initializes a new instance of the <see cref="MouseHandler" /> class.
          /// </summary>
          /// <param name="addPointer">A function called when a new pointer is detected.</param>
          /// <param name="updatePointer">A function called when a pointer is moved or its parameter is updated.</param>
          /// <param name="pressPointer">A function called when a pointer touches the surface.</param>
          /// <param name="releasePointer">A function called when a pointer is lifted off.</param>
          /// <param name="removePointer">A function called when a pointer is removed.</param>
          /// <param name="cancelPointer">A function called when a pointer is cancelled.</param>
          public MouseHandler(PointerDelegate addPointer, PointerDelegate updatePointer, PointerDelegate pressPointer, PointerDelegate releasePointer, PointerDelegate removePointer, PointerDelegate cancelPointer)
          {
              this.addPointer = addPointer;
              this.updatePointer = updatePointer;
              this.pressPointer = pressPointer;
              this.releasePointer = releasePointer;
              this.removePointer = removePointer;
              this.cancelPointer = cancelPointer;
  
              mousePool = new ObjectPool<MousePointer>(4, () => new MousePointer(this), null, resetPointer);
  
              mousePointPos = Input.mousePosition;
              mousePointer = internalAddPointer(remapCoordinates(mousePointPos));
  
              stateMouse();
          }
  
          #region Public methods
  
          /// <summary>
          /// Cancels the mouse pointer.
          /// </summary>
          public void CancelMousePointer()
          {
              if (mousePointer != null)
              {
                  cancelPointer(mousePointer);
                  mousePointer = null;
              }
          }
  
          /// <inheritdoc />
          public bool UpdateInput()
          {
              var pos = Input.mousePosition;
              Vector2 remappedPos = new Vector2(0, 0);
              bool updated = false;
  
              if (mousePointPos != pos)
              {
                  remappedPos = remapCoordinates(new Vector2(pos.x, pos.y));
  
                  if (mousePointer == null)
                  {
                      mousePointer = internalAddPointer(remappedPos);
                  }
                  else
                  {
                      mousePointer.Position = remappedPos;
                      updatePointer(mousePointer);
                  }
                  updated = true;
              }
  
              if (mousePointer == null) return false;
  
              var buttons = state == State.MouseAndFake ? fakeMousePointer.Buttons : mousePointer.Buttons;
              var newButtons = getMouseButtons();
              var scroll = Input.mouseScrollDelta;
              if (!Mathf.Approximately(scroll.sqrMagnitude, 0.0f))
              {
                  mousePointer.ScrollDelta = scroll;
                  updatePointer(mousePointer);
              }
              else
              {
                  mousePointer.ScrollDelta = Vector2.zero;
              }
  
              if (emulateSecondMousePointer)
              {
                  switch (state)
                  {
                      case State.Mouse:
                          if (Input.GetKeyDown(KeyCode.LeftAlt) && !Input.GetKeyUp(KeyCode.LeftAlt)
                              && ((newButtons & Pointer.PointerButtonState.AnyButtonPressed) == 0))
                          {
                              stateWaitingForFake();
                          }
                          else
                          {
                              if (buttons != newButtons) updateButtons(buttons, newButtons);
                          }
                          break;
                      case State.WaitingForFake:
                          if (Input.GetKey(KeyCode.LeftAlt))
                          {
                              if ((newButtons & Pointer.PointerButtonState.AnyButtonDown) != 0)
                              {
                                  // A button is down while holding Alt
                                  fakeMousePointer = internalAddPointer(pos, newButtons, mousePointer.Flags | Pointer.FLAG_ARTIFICIAL);
                                  pressPointer(fakeMousePointer);
                                  stateMouseAndFake();
                              }
                          }
                          else
                          {
                              stateMouse();
                          }
                          break;
                      case State.MouseAndFake:
                          if (fakeTouchReleased())
                          {
                              stateMouse();
                          }
                          else
                          {
                              if (mousePointPos != pos)
                              {
                                  fakeMousePointer.Position = remappedPos;
                                  updatePointer(fakeMousePointer);
                              }
                              if ((newButtons & Pointer.PointerButtonState.AnyButtonPressed) == 0)
                              {
                                  // All buttons are released, Alt is still holding
                                  stateStationaryFake();
                              }
                              else if (buttons != newButtons)
                              {
                                  fakeMousePointer.Buttons = newButtons;
                                  updatePointer(fakeMousePointer);
                              }
                          }
                          break;
                      case State.StationaryFake:
                          if (buttons != newButtons) updateButtons(buttons, newButtons);
                          if (fakeTouchReleased())
                          {
                              stateMouse();
                          }
                          break;
                  }
              }
              else
              {
                  if (buttons != newButtons)
                  {
                      updateButtons(buttons, newButtons);
                      updated = true;
                  }
              }
  
              mousePointPos = pos;
              return updated;
          }
  
          /// <inheritdoc />
          public void UpdateResolution()
          {
              TouchManager.Instance.CancelPointer(mousePointer.Id);
          }
  
          /// <inheritdoc />
          public bool CancelPointer(Pointer pointer, bool shouldReturn)
          {
              if (pointer.Equals(mousePointer))
              {
                  cancelPointer(mousePointer);
                  if (shouldReturn) mousePointer = internalReturnPointer(mousePointer);
                  else mousePointer = internalAddPointer(mousePointer.Position); // can't totally cancel mouse pointer
                  return true;
              }
              if (pointer.Equals(fakeMousePointer))
              {
                  cancelPointer(fakeMousePointer);
                  if (shouldReturn) fakeMousePointer = internalReturnPointer(fakeMousePointer);
                  else fakeMousePointer = null;
                  return true;
              }
              return false;
          }
  
          /// <summary>
          /// Releases resources.
          /// </summary>
          public void Dispose()
          {
              if (mousePointer != null)
              {
                  cancelPointer(mousePointer);
                  mousePointer = null;
              }
              if (fakeMousePointer != null)
              {
                  cancelPointer(fakeMousePointer);
                  fakeMousePointer = null;
              }
          }
  
          #endregion
  
          #region Internal methods
  
          /// <inheritdoc />
          public void INTERNAL_DiscardPointer(Pointer pointer)
          {
              var p = pointer as MousePointer;
  
gt:             if (p == null) return;