Geen omschrijving
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Touchscreen.cs 48KB

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