Touchscreen.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. using System;
  2. using System.Runtime.InteropServices;
  3. using Unity.Collections.LowLevel.Unsafe;
  4. using UnityEngine.InputSystem.Controls;
  5. using UnityEngine.InputSystem.Layouts;
  6. using UnityEngine.InputSystem.LowLevel;
  7. using UnityEngine.InputSystem.Utilities;
  8. using UnityEngine.Profiling;
  9. ////TODO: property that tells whether a Touchscreen is multi-touch capable
  10. ////TODO: property that tells whether a Touchscreen supports pressure
  11. ////TODO: add support for screen orientation
  12. ////TODO: touch is hardwired to certain memory layouts ATM; either allow flexibility or make sure the layouts cannot be changed
  13. ////TODO: startTimes are baked *external* times; reset touch when coming out of play mode
  14. ////REVIEW: where should we put handset vibration support? should that sit on the touchscreen class? be its own separate device?
  15. namespace UnityEngine.InputSystem.LowLevel
  16. {
  17. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "byte to correspond to TouchState layout.")]
  18. [Flags]
  19. internal enum TouchFlags : byte
  20. {
  21. // NOTE: Leaving the first 4 bits for native.
  22. IndirectTouch = 1 << 0,
  23. PrimaryTouch = 1 << 4,
  24. Tap = 1 << 5,
  25. // Indicates that the touch that established this primary touch has ended but that when
  26. // it did, there were still other touches going on. We end the primary touch when the
  27. // last touch leaves the screen.
  28. OrphanedPrimaryTouch = 1 << 6,
  29. }
  30. ////REVIEW: add timestamp directly to touch?
  31. /// <summary>
  32. /// State layout for a single touch.
  33. /// </summary>
  34. /// <remarks>
  35. /// This is the low-level memory representation of a single touch, i.e the
  36. /// way touches are internally transmitted and stored in the system. To update
  37. /// touches on a <see cref="Touchscreen"/>, <see cref="StateEvent"/>s containing
  38. /// TouchStates are sent to the screen.
  39. /// </remarks>
  40. /// <seealso cref="TouchControl"/>
  41. /// <seealso cref="Touchscreen"/>
  42. // IMPORTANT: Must match TouchInputState in native code.
  43. [StructLayout(LayoutKind.Explicit, Size = kSizeInBytes)]
  44. public struct TouchState : IInputStateTypeInfo
  45. {
  46. internal const int kSizeInBytes = 56;
  47. /// <summary>
  48. /// Memory format tag for TouchState.
  49. /// </summary>
  50. /// <value>Returns "TOUC".</value>
  51. /// <seealso cref="InputStateBlock.format"/>
  52. public static FourCC Format => new FourCC('T', 'O', 'U', 'C');
  53. /// <summary>
  54. /// Numeric ID of the touch.
  55. /// </summary>
  56. /// <value>Numeric ID of the touch.</value>
  57. /// <remarks>
  58. /// While a touch is ongoing, it must have a non-zero ID different from
  59. /// all other ongoing touches. Starting with <see cref="TouchPhase.Began"/>
  60. /// and ending with <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/>,
  61. /// a touch is identified by its ID, i.e. a TouchState with the same ID
  62. /// belongs to the same touch.
  63. ///
  64. /// After a touch has ended or been canceled, an ID can be reused.
  65. /// </remarks>
  66. /// <seealso cref="TouchControl.touchId"/>
  67. [InputControl(displayName = "Touch ID", layout = "Integer", synthetic = true)]
  68. [FieldOffset(0)]
  69. public int touchId;
  70. /// <summary>
  71. /// Screen-space position of the touch in pixels.
  72. /// </summary>
  73. /// <value>Screen-space position of the touch.</value>
  74. /// <seealso cref="TouchControl.position"/>
  75. [InputControl(displayName = "Position")]
  76. [FieldOffset(4)]
  77. public Vector2 position;
  78. /// <summary>
  79. /// Screen-space motion delta of the touch in pixels.
  80. /// </summary>
  81. /// <value>Screen-space movement delta.</value>
  82. /// <seealso cref="TouchControl.delta"/>
  83. [InputControl(displayName = "Delta")]
  84. [FieldOffset(12)]
  85. public Vector2 delta;
  86. /// <summary>
  87. /// Pressure-level of the touch against the touchscreen.
  88. /// </summary>
  89. /// <value>Pressure of touch.</value>
  90. /// <remarks>
  91. /// The core range for this value is [0..1] with 1 indicating maximum pressure. Note, however,
  92. /// that the actual value may go beyond 1 in practice. This is because the system will usually
  93. /// define "maximum pressure" to be less than the physical maximum limit the hardware is capable
  94. /// of reporting so that to achieve maximum pressure, one does not need to press as hard as
  95. /// possible.
  96. /// </remarks>
  97. /// <seealso cref="TouchControl.pressure"/>
  98. [InputControl(displayName = "Pressure", layout = "Axis")]
  99. [FieldOffset(20)]
  100. public float pressure;
  101. /// <summary>
  102. /// Radius of the touch print on the surface.
  103. /// </summary>
  104. /// <value>Touch extents horizontally and vertically.</value>
  105. /// <remarks>
  106. /// The touch radius is given in screen-space pixel coordinates along X and Y centered in the middle
  107. /// of the touch. Note that not all screens and systems support radius detection on touches so this
  108. /// value may be at <c>default</c> for an otherwise perfectly valid touch.
  109. /// </remarks>
  110. /// <seealso cref="TouchControl.radius"/>
  111. [InputControl(displayName = "Radius")]
  112. [FieldOffset(24)]
  113. public Vector2 radius;
  114. /// <summary>
  115. /// <see cref="TouchPhase"/> value of the touch.
  116. /// </summary>
  117. /// <value>Current <see cref="TouchPhase"/>.</value>
  118. /// <seealso cref="phase"/>
  119. [InputControl(name = "phase", displayName = "Touch Phase", layout = "TouchPhase", synthetic = true)]
  120. [InputControl(name = "press", displayName = "Touch Contact?", layout = "TouchPress", useStateFrom = "phase")]
  121. [FieldOffset(32)]
  122. public byte phaseId;
  123. [InputControl(name = "tapCount", displayName = "Tap Count", layout = "Integer")]
  124. [FieldOffset(33)]
  125. public byte tapCount;
  126. // Not currently used, but still needed in this struct for padding,
  127. // as il2cpp does not implement FieldOffset.
  128. [FieldOffset(34)]
  129. byte displayIndex;
  130. [InputControl(name = "indirectTouch", displayName = "Indirect Touch?", layout = "Button", bit = 0, synthetic = true)]
  131. [InputControl(name = "tap", displayName = "Tap", layout = "Button", bit = 5)]
  132. [FieldOffset(35)]
  133. public byte flags;
  134. // Wasting four bytes in the name of alignment here. Need the explicit fields as il2cpp doesn't respect
  135. // the explicit field offsets.
  136. [FieldOffset(36)]
  137. internal int padding;
  138. // NOTE: The following data is NOT sent by native but rather data we add on the managed side to each touch.
  139. /// <summary>
  140. /// Time that the touch was started. Relative to <c>Time.realTimeSinceStartup</c>.
  141. /// </summary>
  142. /// <value>Time that the touch was started.</value>
  143. /// <remarks>
  144. /// This is set automatically by <see cref="Touchscreen"/> and does not need to be provided
  145. /// by events sent to the touchscreen.
  146. /// </remarks>
  147. /// <seealso cref="InputEvent.time"/>
  148. /// <seealso cref="TouchControl.startTime"/>
  149. [InputControl(displayName = "Start Time", layout = "Double", synthetic = true)]
  150. [FieldOffset(40)]
  151. public double startTime; // In *external* time, i.e. currentTimeOffsetToRealtimeSinceStartup baked in.
  152. /// <summary>
  153. /// The position where the touch started.
  154. /// </summary>
  155. /// <value>Screen-space start position of the touch.</value>
  156. /// <remarks>
  157. /// This is set automatically by <see cref="Touchscreen"/> and does not need to be provided
  158. /// by events sent to the touchscreen.
  159. /// </remarks>
  160. /// <seealso cref="TouchControl.startPosition"/>
  161. [InputControl(displayName = "Start Position", synthetic = true)]
  162. [FieldOffset(48)]
  163. public Vector2 startPosition;
  164. /// <summary>
  165. /// Get or set the phase of the touch.
  166. /// </summary>
  167. /// <value>Phase of the touch.</value>
  168. /// <seealso cref="TouchControl.phase"/>
  169. public TouchPhase phase
  170. {
  171. get => (TouchPhase)phaseId;
  172. set => phaseId = (byte)value;
  173. }
  174. public bool isNoneEndedOrCanceled => phase == TouchPhase.None || phase == TouchPhase.Ended ||
  175. phase == TouchPhase.Canceled;
  176. public bool isInProgress => phase == TouchPhase.Began || phase == TouchPhase.Moved ||
  177. phase == TouchPhase.Stationary;
  178. /// <summary>
  179. /// Whether TODO
  180. /// </summary>
  181. /// <value>Whether the touch is the first TODO</value>
  182. /// <remarks>
  183. /// This flag will be set internally by <see cref="Touchscreen"/>. Generally, it is
  184. /// not necessary to set this bit manually when feeding data to Touchscreens.
  185. /// </remarks>
  186. public bool isPrimaryTouch
  187. {
  188. get => (flags & (byte)TouchFlags.PrimaryTouch) != 0;
  189. set
  190. {
  191. if (value)
  192. flags |= (byte)TouchFlags.PrimaryTouch;
  193. else
  194. flags &= (byte)~TouchFlags.PrimaryTouch;
  195. }
  196. }
  197. internal bool isOrphanedPrimaryTouch
  198. {
  199. get => (flags & (byte)TouchFlags.OrphanedPrimaryTouch) != 0;
  200. set
  201. {
  202. if (value)
  203. flags |= (byte)TouchFlags.OrphanedPrimaryTouch;
  204. else
  205. flags &= (byte)~TouchFlags.OrphanedPrimaryTouch;
  206. }
  207. }
  208. public bool isIndirectTouch
  209. {
  210. get => (flags & (byte)TouchFlags.IndirectTouch) != 0;
  211. set
  212. {
  213. if (value)
  214. flags |= (byte)TouchFlags.IndirectTouch;
  215. else
  216. flags &= (byte)~TouchFlags.IndirectTouch;
  217. }
  218. }
  219. public bool isTap
  220. {
  221. get => (flags & (byte)TouchFlags.Tap) != 0;
  222. set
  223. {
  224. if (value)
  225. flags |= (byte)TouchFlags.Tap;
  226. else
  227. flags &= (byte)~TouchFlags.Tap;
  228. }
  229. }
  230. /// <inheritdoc/>
  231. public FourCC format => Format;
  232. /// <summary>
  233. /// Return a string representation of the state useful for debugging.
  234. /// </summary>
  235. /// <returns>A string representation of the touch state.</returns>
  236. public override string ToString()
  237. {
  238. return $"{{ id={touchId} phase={phase} pos={position} delta={delta} pressure={pressure} radius={radius} primary={isPrimaryTouch} }}";
  239. }
  240. }
  241. /// <summary>
  242. /// Default state layout for touch devices.
  243. /// </summary>
  244. /// <remarks>
  245. /// Combines multiple pointers each corresponding to a single contact.
  246. ///
  247. /// Normally, TODO (sending state events)
  248. ///
  249. /// All touches combine to quite a bit of state; ideally send delta events that update
  250. /// only specific fingers.
  251. ///
  252. /// This is NOT used by native. Instead, the native runtime always sends individual touches (<see cref="TouchState"/>)
  253. /// and leaves state management for a touchscreen as a whole to the managed part of the system.
  254. /// </remarks>
  255. [StructLayout(LayoutKind.Explicit, Size = MaxTouches * TouchState.kSizeInBytes)]
  256. internal unsafe struct TouchscreenState : IInputStateTypeInfo
  257. {
  258. /// <summary>
  259. /// Memory format tag for TouchscreenState.
  260. /// </summary>
  261. /// <value>Returns "TSCR".</value>
  262. /// <seealso cref="InputStateBlock.format"/>
  263. public static FourCC Format => new FourCC('T', 'S', 'C', 'R');
  264. /// <summary>
  265. /// Maximum number of touches that can be tracked at the same time.
  266. /// </summary>
  267. /// <value>Maximum number of concurrent touches.</value>
  268. public const int MaxTouches = 10;
  269. /// <summary>
  270. /// Data for the touch that is deemed the "primary" touch at the moment.
  271. /// </summary>
  272. /// <remarks>
  273. /// This touch duplicates touch data from whichever touch is deemed the primary touch at the moment.
  274. /// When going from no fingers down to any finger down, the first finger to touch the screen is
  275. /// deemed the "primary touch". It stays the primary touch until released. At that point, if any other
  276. /// finger is still down, the next finger in <see cref="touchData"/> is
  277. ///
  278. /// Having this touch be its own separate state and own separate control allows actions to track the
  279. /// state of the primary touch even if the touch moves from one finger to another in <see cref="touchData"/>.
  280. /// </remarks>
  281. [InputControl(name = "primaryTouch", displayName = "Primary Touch", layout = "Touch", synthetic = true)]
  282. [InputControl(name = "primaryTouch/tap", usage = "PrimaryAction")]
  283. // Add controls compatible with what Pointer expects and redirect their
  284. // state to the state of touch0 so that this essentially becomes our
  285. // pointer control.
  286. // NOTE: Some controls from Pointer don't make sense for touch and we "park"
  287. // them by assigning them invalid offsets (thus having automatic state
  288. // layout put them at the end of our fixed state).
  289. [InputControl(name = "position", useStateFrom = "primaryTouch/position")]
  290. [InputControl(name = "delta", useStateFrom = "primaryTouch/delta")]
  291. [InputControl(name = "pressure", useStateFrom = "primaryTouch/pressure")]
  292. [InputControl(name = "radius", useStateFrom = "primaryTouch/radius")]
  293. [InputControl(name = "press", useStateFrom = "primaryTouch/phase", layout = "TouchPress", synthetic = true, usages = new string[0])]
  294. [FieldOffset(0)]
  295. public fixed byte primaryTouchData[TouchState.kSizeInBytes];
  296. internal const int kTouchDataOffset = TouchState.kSizeInBytes;
  297. [InputControl(layout = "Touch", name = "touch", displayName = "Touch", arraySize = MaxTouches)]
  298. [FieldOffset(kTouchDataOffset)]
  299. public fixed byte touchData[MaxTouches * TouchState.kSizeInBytes];
  300. public TouchState* primaryTouch
  301. {
  302. get
  303. {
  304. fixed(byte* ptr = primaryTouchData)
  305. return (TouchState*)ptr;
  306. }
  307. }
  308. public TouchState* touches
  309. {
  310. get
  311. {
  312. fixed(byte* ptr = touchData)
  313. return (TouchState*)ptr;
  314. }
  315. }
  316. public FourCC format => Format;
  317. }
  318. }
  319. namespace UnityEngine.InputSystem
  320. {
  321. /// <summary>
  322. /// Indicates where in its lifecycle a given touch is.
  323. /// </summary>
  324. public enum TouchPhase
  325. {
  326. /// <summary>
  327. /// No activity has been registered on the touch yet.
  328. /// </summary>
  329. /// <remarks>
  330. /// A given touch state will generally not go back to None once there has been input for it. Meaning that
  331. /// it generally indicates a default-initialized touch record.
  332. /// </remarks>
  333. None,
  334. /// <summary>
  335. /// A touch has just begun, i.e. a finger has touched the screen.. Only the first touch input in any given touch will have this phase.
  336. /// </summary>
  337. Began,
  338. /// <summary>
  339. /// An ongoing touch has changed position.
  340. /// </summary>
  341. Moved,
  342. /// <summary>
  343. /// An ongoing touch has just ended, i.e. the respective finger has been lifted off of the screen. Only the last touch input in a
  344. /// given touch will have this phase.
  345. /// </summary>
  346. Ended,
  347. /// <summary>
  348. /// An ongoing touch has been cancelled, i.e. ended in a way other than through user interaction. This happens, for example, if
  349. /// focus is moved away from the application while the touch is ongoing.
  350. /// </summary>
  351. Canceled,
  352. /// <summary>
  353. /// An ongoing touch has not been moved (not received any input) in a frame.
  354. /// </summary>
  355. /// <remarks>
  356. /// This phase is not used by <see cref="Touchscreen"/>. This means that <see cref="TouchControl"/> will not generally
  357. /// return this value for <see cref="TouchControl.phase"/>. It is, however, used by <see cref="UnityEngine.InputSystem.EnhancedTouch.Touch"/>.
  358. /// </remarks>
  359. Stationary,
  360. }
  361. /// <summary>
  362. /// A multi-touch surface.
  363. /// </summary>
  364. /// <remarks>
  365. /// Touchscreen is somewhat different from most other device implementations in that it does not usually
  366. /// consume input in the form of a full device snapshot but rather consumes input sent to it in the form
  367. /// of events containing a <see cref="TouchState"/> each. This is unusual as <see cref="TouchState"/>
  368. /// uses a memory format different from <see cref="TouchState.Format"/>. However, when a <c>Touchscreen</c>
  369. /// sees an event containing a <see cref="TouchState"/>, it will handle that event on a special code path.
  370. ///
  371. /// This allows <c>Touchscreen</c> to decide on its own which control in <see cref="touches"/> to store
  372. /// a touch at and to perform things such as tap detection (see <see cref="TouchControl.tap"/> and
  373. /// <see cref="TouchControl.tapCount"/>) and primary touch handling (see <see cref="primaryTouch"/>).
  374. ///
  375. /// <example>
  376. /// <code>
  377. /// // Create a touchscreen device.
  378. /// var touchscreen = InputSystem.AddDevice&lt;Touchscreen&gt;();
  379. ///
  380. /// // Send a touch to the device.
  381. /// InputSystem.QueueStateEvent(touchscreen,
  382. /// new TouchState
  383. /// {
  384. /// phase = TouchPhase.Began,
  385. /// // Must have a valid, non-zero touch ID. Touchscreen will not operate
  386. /// // correctly if we don't set IDs properly.
  387. /// touchId = 1,
  388. /// position = new Vector2(123, 234),
  389. /// // Delta will be computed by Touchscreen automatically.
  390. /// });
  391. /// </code>
  392. /// </example>
  393. ///
  394. /// Note that this class presents a fairly low-level touch API. When working with touch from script code,
  395. /// it is recommended to use the higher-level <see cref="EnhancedTouch.Touch"/> API instead.
  396. /// </remarks>
  397. [InputControlLayout(stateType = typeof(TouchscreenState), isGenericTypeOfDevice = true)]
  398. [Scripting.Preserve]
  399. public class Touchscreen : Pointer, IInputStateCallbackReceiver
  400. {
  401. /// <summary>
  402. /// Synthetic control that has the data for the touch that is deemed the "primary" touch at the moment.
  403. /// </summary>
  404. /// <value>Control tracking the screen's primary touch.</value>
  405. /// <remarks>
  406. /// This touch duplicates touch data from whichever touch is deemed the primary touch at the moment.
  407. /// When going from no fingers down to any finger down, the first finger to touch the screen is
  408. /// deemed the "primary touch". It stays the primary touch until the last finger is released.
  409. ///
  410. /// Note that unlike the touch from which it originates, the primary touch will be kept ongoing for
  411. /// as long as there is still a finger on the screen. Put another way, <see cref="TouchControl.phase"/>
  412. /// of <c>primaryTouch</c> will only transition to <see cref="TouchPhase.Ended"/> once the last finger
  413. /// has been lifted off the screen.
  414. /// </remarks>
  415. public TouchControl primaryTouch { get; private set; }
  416. /// <summary>
  417. /// Array of all <see cref="TouchControl"/>s on the device.
  418. /// </summary>
  419. /// <value>All <see cref="TouchControl"/>s on the screen.</value>
  420. /// <remarks>
  421. /// By default, a touchscreen will allocate 10 touch controls. This can be changed
  422. /// by modifying the "Touchscreen" layout itself or by derived layouts. In practice,
  423. /// this means that this array will usually have a fixed length of 10 entries but
  424. /// it may deviate from that.
  425. /// </remarks>
  426. public ReadOnlyArray<TouchControl> touches { get; private set; }
  427. /// <summary>
  428. /// The touchscreen that was added or updated last or null if there is no
  429. /// touchscreen connected to the system.
  430. /// </summary>
  431. /// <value>Current touch screen.</value>
  432. public new static Touchscreen current { get; internal set; }
  433. /// <inheritdoc />
  434. public override void MakeCurrent()
  435. {
  436. base.MakeCurrent();
  437. current = this;
  438. }
  439. /// <inheritdoc />
  440. protected override void OnRemoved()
  441. {
  442. base.OnRemoved();
  443. if (current == this)
  444. current = null;
  445. }
  446. /// <inheritdoc />
  447. protected override void FinishSetup()
  448. {
  449. base.FinishSetup();
  450. primaryTouch = GetChildControl<TouchControl>("primaryTouch");
  451. // Find out how many touch controls we have.
  452. var touchControlCount = 0;
  453. foreach (var child in children)
  454. if (child is TouchControl)
  455. ++touchControlCount;
  456. // Keep primaryTouch out of array.
  457. Debug.Assert(touchControlCount >= 1, "Should have found at least primaryTouch control");
  458. if (touchControlCount >= 1)
  459. --touchControlCount;
  460. // Gather touch controls into array.
  461. var touchArray = new TouchControl[touchControlCount];
  462. var touchIndex = 0;
  463. foreach (var child in children)
  464. {
  465. if (child == primaryTouch)
  466. continue;
  467. if (child is TouchControl control)
  468. touchArray[touchIndex++] = control;
  469. }
  470. touches = new ReadOnlyArray<TouchControl>(touchArray);
  471. }
  472. // Touch has more involved state handling than most other devices. To not put touch allocation logic
  473. // in all the various platform backends (i.e. see a touch with a certain ID coming in from the system
  474. // and then having to decide *where* to store that inside of Touchscreen's state), we have backends
  475. // send us individual touches ('TOUC') instead of whole Touchscreen snapshots ('TSRC'). Using
  476. // IInputStateCallbackReceiver, Touchscreen then dynamically decides where to store the touch.
  477. //
  478. // Also, Touchscreen has bits of logic to automatically synthesize the state of controls it inherits
  479. // from Pointer (such as "<Pointer>/press").
  480. //
  481. // NOTE: We do *NOT* make a effort here to prevent us from losing short-lived touches. This is different
  482. // from the old input system where individual touches were not reused until the next frame. This meant
  483. // that additional touches potentially had to be allocated in order to accomodate new touches coming
  484. // in from the system.
  485. //
  486. // The rationale for *NOT* doing this is that:
  487. //
  488. // a) Actions don't need it. They observe every single state change and thus will not lose data
  489. // even if it is short-lived (i.e. changes more than once in the same update).
  490. // b) The higher-level Touch (EnhancedTouchSupport) API is provided to
  491. // not only handle this scenario but also give a generally more flexible and useful touch API
  492. // than writing code directly against Touchscreen.
  493. protected new unsafe void OnNextUpdate()
  494. {
  495. Profiler.BeginSample("Touchscreen.OnNextUpdate");
  496. ////TODO: early out and skip crawling through touches if we didn't change state in the last update
  497. //// (also obsoletes the need for the if() check below)
  498. var statePtr = currentStatePtr;
  499. var touchStatePtr = (TouchState*)((byte*)statePtr + stateBlock.byteOffset + TouchscreenState.kTouchDataOffset);
  500. for (var i = 0; i < touches.Count; ++i, ++touchStatePtr)
  501. {
  502. // Reset delta.
  503. if (touchStatePtr->delta != default)
  504. InputState.Change(touches[i].delta, Vector2.zero);
  505. // Reset tap count.
  506. // NOTE: We are basing this on startTime rather than adding on end time of the last touch. The reason is
  507. // that to do so we would have to add another record to keep track of timestamps for each touch. And
  508. // since we know the maximum time that a tap can take, we have a reasonable estimate for when a prior
  509. // tap must have ended.
  510. if (touchStatePtr->tapCount > 0 && InputState.currentTime >= touchStatePtr->startTime + s_TapTime + s_TapDelayTime)
  511. InputState.Change(touches[i].tapCount, (byte)0);
  512. }
  513. var primaryTouchState = (TouchState*)((byte*)statePtr + stateBlock.byteOffset);
  514. if (primaryTouchState->delta != default)
  515. InputState.Change(primaryTouch.delta, Vector2.zero);
  516. if (primaryTouchState->tapCount > 0 && InputState.currentTime >= primaryTouchState->startTime + s_TapTime + s_TapDelayTime)
  517. InputState.Change(primaryTouch.tapCount, (byte)0);
  518. Profiler.EndSample();
  519. }
  520. /// <summary>
  521. /// Called whenever a new state event is received.
  522. /// </summary>
  523. /// <param name="eventPtr"></param>
  524. protected new unsafe void OnStateEvent(InputEventPtr eventPtr)
  525. {
  526. // If it's not a single touch, just take the event state as is (will have to be TouchscreenState).
  527. if (eventPtr.stateFormat != TouchState.Format)
  528. {
  529. InputState.Change(this, eventPtr);
  530. return;
  531. }
  532. // We don't allow partial updates for TouchStates.
  533. if (eventPtr.IsA<DeltaStateEvent>())
  534. return;
  535. Profiler.BeginSample("TouchAllocate");
  536. // For performance reasons, we read memory here directly rather than going through
  537. // ReadValue() of the individual TouchControl children. This means that Touchscreen,
  538. // unlike other devices, is hardwired to a single memory layout only.
  539. var stateEventPtr = StateEvent.From(eventPtr);
  540. var statePtr = currentStatePtr;
  541. var currentTouchState = (TouchState*)((byte*)statePtr + touches[0].stateBlock.byteOffset);
  542. var primaryTouchState = (TouchState*)((byte*)statePtr + primaryTouch.stateBlock.byteOffset);
  543. var touchControlCount = touches.Count;
  544. // Native does not send a full TouchState as we define it here. We have added some fields
  545. // that we store internally. Make sure we don't read invalid memory here and copy only what
  546. // we got.
  547. TouchState newTouchState;
  548. if (stateEventPtr->stateSizeInBytes == TouchState.kSizeInBytes)
  549. {
  550. newTouchState = *(TouchState*)stateEventPtr->state;
  551. }
  552. else
  553. {
  554. newTouchState = new TouchState();
  555. UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref newTouchState), stateEventPtr->state, stateEventPtr->stateSizeInBytes);
  556. }
  557. // Make sure we're not getting thrown off by noise on fields that we don't want to
  558. // pick up from input.
  559. newTouchState.tapCount = 0;
  560. newTouchState.isTap = false;
  561. ////REVIEW: The logic in here makes us inherently susceptible to the ordering of the touch events in the event
  562. //// stream. I believe we have platforms (Android?) that send us touch events finger-by-finger (or touch-by-touch?)
  563. //// rather than sorted by time. This will probably screw up the logic in here.
  564. // If it's an ongoing touch, try to find the TouchState we have allocated to the touch
  565. // previously.
  566. var phase = newTouchState.phase;
  567. if (phase != TouchPhase.Began)
  568. {
  569. var touchId = newTouchState.touchId;
  570. for (var i = 0; i < touchControlCount; ++i)
  571. {
  572. if (currentTouchState[i].touchId == touchId)
  573. {
  574. // Preserve primary touch state.
  575. var isPrimaryTouch = currentTouchState[i].isPrimaryTouch;
  576. newTouchState.isPrimaryTouch = isPrimaryTouch;
  577. // Compute delta if touch doesn't have one.
  578. if (newTouchState.delta == default)
  579. newTouchState.delta = newTouchState.position - currentTouchState[i].position;
  580. // Accumulate delta.
  581. newTouchState.delta += currentTouchState[i].delta;
  582. // Keep start time and position.
  583. newTouchState.startTime = currentTouchState[i].startTime;
  584. newTouchState.startPosition = currentTouchState[i].startPosition;
  585. // Detect taps.
  586. var isTap = newTouchState.isNoneEndedOrCanceled &&
  587. (eventPtr.time - newTouchState.startTime) <= s_TapTime &&
  588. ////REVIEW: this only takes the final delta to start position into account, not the delta over the lifetime of the
  589. //// touch; is this robust enough or do we need to make sure that we never move more than the tap radius
  590. //// over the entire lifetime of the touch?
  591. (newTouchState.position - newTouchState.startPosition).sqrMagnitude <= s_TapRadiusSquared;
  592. if (isTap)
  593. newTouchState.tapCount = (byte)(currentTouchState[i].tapCount + 1);
  594. else
  595. newTouchState.tapCount = currentTouchState[i].tapCount; // Preserve tap count; reset in OnCarryStateForward.
  596. // Update primary touch.
  597. if (isPrimaryTouch)
  598. {
  599. if (newTouchState.isNoneEndedOrCanceled)
  600. {
  601. ////REVIEW: also reset tapCounts here when tap delay time has expired on the touch?
  602. newTouchState.isPrimaryTouch = false;
  603. // Primary touch was ended. See if there are still other ongoing touches.
  604. var haveOngoingTouch = false;
  605. for (var n = 0; n < touchControlCount; ++n)
  606. {
  607. if (n == i)
  608. continue;
  609. if (currentTouchState[n].isInProgress)
  610. {
  611. haveOngoingTouch = true;
  612. break;
  613. }
  614. }
  615. if (!haveOngoingTouch)
  616. {
  617. // No, primary was the only ongoing touch. End it.
  618. if (isTap)
  619. TriggerTap(primaryTouch, ref newTouchState, eventPtr);
  620. else
  621. InputState.Change(primaryTouch, newTouchState, eventPtr: eventPtr);
  622. }
  623. else
  624. {
  625. // Yes, we have other touches going on. Make the primary touch an
  626. // orphan and wait until the other touches are released.
  627. var newPrimaryTouchState = newTouchState;
  628. newPrimaryTouchState.phase = TouchPhase.Moved;
  629. newPrimaryTouchState.isOrphanedPrimaryTouch = true;
  630. InputState.Change(primaryTouch, newPrimaryTouchState, eventPtr: eventPtr);
  631. }
  632. }
  633. else
  634. {
  635. // Primary touch was updated.
  636. InputState.Change(primaryTouch, newTouchState, eventPtr: eventPtr);
  637. }
  638. }
  639. else
  640. {
  641. // If it's not the primary touch but the touch has ended, see if we have an
  642. // orphaned primary touch. If so, end it now.
  643. if (newTouchState.isNoneEndedOrCanceled && primaryTouchState->isOrphanedPrimaryTouch)
  644. {
  645. var haveOngoingTouch = false;
  646. for (var n = 0; n < touchControlCount; ++n)
  647. {
  648. if (n == i)
  649. continue;
  650. if (currentTouchState[n].isInProgress)
  651. {
  652. haveOngoingTouch = true;
  653. break;
  654. }
  655. }
  656. if (!haveOngoingTouch)
  657. {
  658. primaryTouchState->isOrphanedPrimaryTouch = false;
  659. InputState.Change(primaryTouch.phase, (byte)TouchPhase.Ended);
  660. }
  661. }
  662. }
  663. if (isTap)
  664. {
  665. // Make tap button go down and up.
  666. //
  667. // NOTE: We do this here instead of right away up there when we detect the touch so
  668. // that the state change notifications go together. First those for the primary
  669. // touch, then the ones for the touch record itself.
  670. TriggerTap(touches[i], ref newTouchState, eventPtr);
  671. }
  672. else
  673. {
  674. InputState.Change(touches[i], newTouchState, eventPtr: eventPtr);
  675. }
  676. Profiler.EndSample();
  677. return;
  678. }
  679. }
  680. // Couldn't find an entry. Either it was a touch that we previously ran out of available
  681. // entries for or it's an event sent out of sequence. Ignore the touch to be consistent.
  682. Profiler.EndSample();
  683. return;
  684. }
  685. // It's a new touch. Try to find an unused TouchState.
  686. for (var i = 0; i < touchControlCount; ++i, ++currentTouchState)
  687. {
  688. // NOTE: We're overwriting any ended touch immediately here. This means we immediately overwrite even
  689. // if we still have other unused slots. What this gives us is a completely predictable touch #0..#N
  690. // sequence (i.e. touch #N is only ever used if there are indeed #N concurrently touches). However,
  691. // it does mean that we overwrite state aggressively. If you are not using actions or the higher-level
  692. // Touch API, be aware of this!
  693. if (currentTouchState->isNoneEndedOrCanceled)
  694. {
  695. newTouchState.delta = Vector2.zero;
  696. newTouchState.startTime = eventPtr.time;
  697. newTouchState.startPosition = newTouchState.position;
  698. // Make sure we're not picking up noise sent from native.
  699. newTouchState.isPrimaryTouch = false;
  700. newTouchState.isOrphanedPrimaryTouch = false;
  701. newTouchState.isTap = false;
  702. // Tap counts are preserved from prior touches on the same finger.
  703. newTouchState.tapCount = currentTouchState->tapCount;
  704. // Make primary touch, if there's none currently.
  705. if (primaryTouchState->isNoneEndedOrCanceled)
  706. {
  707. newTouchState.isPrimaryTouch = true;
  708. InputState.Change(primaryTouch, newTouchState, eventPtr: eventPtr);
  709. }
  710. InputState.Change(touches[i], newTouchState, eventPtr: eventPtr);
  711. Profiler.EndSample();
  712. return;
  713. }
  714. }
  715. // We ran out of state and we don't want to stomp an existing ongoing touch.
  716. // Drop this touch entirely.
  717. // NOTE: Getting here means we're having fewer touch entries than the number of concurrent touches supported
  718. // by the backend (or someone is simply sending us nonsense data).
  719. Profiler.EndSample();
  720. }
  721. void IInputStateCallbackReceiver.OnNextUpdate()
  722. {
  723. OnNextUpdate();
  724. }
  725. void IInputStateCallbackReceiver.OnStateEvent(InputEventPtr eventPtr)
  726. {
  727. OnStateEvent(eventPtr);
  728. }
  729. unsafe bool IInputStateCallbackReceiver.GetStateOffsetForEvent(InputControl control, InputEventPtr eventPtr, ref uint offset)
  730. {
  731. // This code goes back to the trickery we perform in OnStateEvent. We consume events in TouchState format
  732. // instead of in TouchscreenState format. This means that the input system does not know how the state in those
  733. // events correlates to the controls we have.
  734. //
  735. // This method is used to give the input system an offset based on which the input system can compute relative
  736. // offsets into the state of eventPtr for controls that are part of the control hierarchy rooted at 'control'.
  737. if (!eventPtr.IsA<StateEvent>() || StateEvent.From(eventPtr)->stateFormat != TouchState.Format)
  738. return false;
  739. // The only controls we can read out from a TouchState event are those that are part of TouchControl
  740. // (and part of this Touchscreen).
  741. var touchControl = control.FindInParentChain<TouchControl>();
  742. if (touchControl == null || touchControl.parent != this)
  743. return false;
  744. // We could allow *any* of the TouchControls on the Touchscreen here. We'd simply base the
  745. // offset on the TouchControl of the 'control' we get as an argument.
  746. //
  747. // However, doing that would mean that all the TouchControls would map into the same input event.
  748. // So when a piece of code like in InputUser goes and cycles through all controls to determine ones
  749. // that have changed in an event, it would find that instead of a single touch position value changing,
  750. // all of them would be changing from the same single event.
  751. //
  752. // For this reason, we lock things down to the primaryTouch control.
  753. if (touchControl != primaryTouch)
  754. return false;
  755. offset = touchControl.stateBlock.byteOffset;
  756. return true;
  757. }
  758. // We can only detect taps on touch *release*. At which point it acts like a button that triggers and releases
  759. // in one operation.
  760. private static void TriggerTap(TouchControl control, ref TouchState state, InputEventPtr eventPtr)
  761. {
  762. ////REVIEW: we're updating the entire TouchControl here; we could update just the tap state using a delta event; problem
  763. //// is that the tap *down* still needs a full update on the state
  764. // We don't increase tapCount here as we may be sending the tap from the same state to both the TouchControl
  765. // that got tapped and to primaryTouch.
  766. // Press.
  767. state.isTap = true;
  768. InputState.Change(control, state, eventPtr: eventPtr);
  769. // Release.
  770. state.isTap = false;
  771. InputState.Change(control, state, eventPtr: eventPtr);
  772. }
  773. internal static float s_TapTime;
  774. internal static float s_TapDelayTime;
  775. internal static float s_TapRadiusSquared;
  776. }
  777. }