暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

InputSystemUIInputModule.cs 113KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398
  1. #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine.EventSystems;
  5. using UnityEngine.InputSystem.Controls;
  6. using UnityEngine.InputSystem.LowLevel;
  7. using UnityEngine.InputSystem.Utilities;
  8. using UnityEngine.Serialization;
  9. using UnityEngine.UI;
  10. #if UNITY_EDITOR
  11. using UnityEditor;
  12. #endif
  13. ////FIXME: The UI is currently not reacting to pointers until they are moved after the UI module has been enabled. What needs to
  14. //// happen is that point, trackedDevicePosition, and trackedDeviceOrientation have initial state checks. However, for touch,
  15. //// we do *not* want to react to the initial value as then we also get presses (unlike with other pointers). Argh.
  16. ////REVIEW: I think this would be much better served by having a composite type input for each of the three basic types of input (pointer, navigation, tracked)
  17. //// I.e. there'd be a PointerInput, a NavigationInput, and a TrackedInput composite. This would solve several problems in one go and make
  18. //// it much more obvious which inputs go together.
  19. //// NOTE: This does not actually solve the problem. Even if, for example, we have a PointerInput value struct and a PointerInputComposite
  20. //// that binds the individual inputs to controls, and then we use it to bind touch0 as a pointer input source, there may still be multiple
  21. //// touchscreens and thus multiple touches coming in through the same composite. This leads back to the same situation.
  22. ////REVIEW: The current input model has too much complexity for pointer input; find a way to simplify this.
  23. ////REVIEW: how does this/uGUI support drag-scrolls on touch? [GESTURES]
  24. ////REVIEW: how does this/uGUI support two-finger right-clicks with touch? [GESTURES]
  25. ////TODO: add ability to query which device was last used with any of the actions
  26. ////REVIEW: also give access to the last/current UI event?
  27. ////TODO: ToString() method a la PointerInputModule
  28. namespace UnityEngine.InputSystem.UI
  29. {
  30. /// <summary>
  31. /// Input module that takes its input from <see cref="InputAction">input actions</see>.
  32. /// </summary>
  33. /// <remarks>
  34. /// This UI input module has the advantage over other such modules that it doesn't have to know
  35. /// what devices and types of devices input is coming from. Instead, the actions hide the actual
  36. /// sources of input from the module.
  37. ///
  38. /// When adding this component from code (such as through <c>GameObject.AddComponent</c>), the
  39. /// resulting module will automatically have a set of default input actions assigned to it
  40. /// (see <see cref="AssignDefaultActions"/>).
  41. /// </remarks>
  42. [HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#setting-up-ui-input")]
  43. public class InputSystemUIInputModule : BaseInputModule
  44. {
  45. /// <summary>
  46. /// Whether to clear the current selection when a click happens that does not hit any <c>GameObject</c>.
  47. /// </summary>
  48. /// <value>If true (default), clicking outside of any GameObject will reset the current selection.</value>
  49. /// <remarks>
  50. /// By toggling this behavior off, background clicks will keep the current selection. I.e.
  51. /// <c>EventSystem.currentSelectedGameObject</c> will not be changed.
  52. /// </remarks>
  53. public bool deselectOnBackgroundClick
  54. {
  55. get => m_DeselectOnBackgroundClick;
  56. set => m_DeselectOnBackgroundClick = value;
  57. }
  58. /// <summary>
  59. /// How to deal with the presence of pointer-type input from multiple devices.
  60. /// </summary>
  61. /// <remarks>
  62. /// By default, this is set to <see cref="UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack"/> which will
  63. /// treat input from <see cref="Mouse"/> and <see cref="Pen"/> devices as coming from a single on-screen pointer
  64. /// but will treat input from devices such as <see cref="XR.XRController"/> and <see cref="Touchscreen"/> as
  65. /// their own discrete pointers.
  66. ///
  67. /// The primary effect of this setting is to determine whether the user can concurrently point at more than
  68. /// a single UI element or not. Whenever multiple pointers are allowed, more than one element may have a pointer
  69. /// over it at any one point and thus several elements can be interacted with concurrently.
  70. /// </remarks>
  71. public UIPointerBehavior pointerBehavior
  72. {
  73. get => m_PointerBehavior;
  74. set => m_PointerBehavior = value;
  75. }
  76. /// <summary>
  77. /// Where to position the pointer when the cursor is locked.
  78. /// </summary>
  79. /// <remarks>
  80. /// By default, the pointer is positioned at -1, -1 in screen space when the cursor is locked. This has implications
  81. /// for using ray casters like <see cref="PhysicsRaycaster"/> because the raycasts will be sent from the pointer
  82. /// position. By setting the value of <see cref="cursorLockBehavior"/> to <see cref="CursorLockBehavior.ScreenCenter"/>,
  83. /// the raycasts will be sent from the center of the screen. This is useful when trying to interact with world space UI
  84. /// using the <see cref="IPointerEnterHandler"/> and <see cref="IPointerExitHandler"/> interfaces when the cursor
  85. /// is locked.
  86. /// </remarks>
  87. /// <see cref="Cursor.lockState"/>
  88. public CursorLockBehavior cursorLockBehavior
  89. {
  90. get => m_CursorLockBehavior;
  91. set => m_CursorLockBehavior = value;
  92. }
  93. /// <summary>
  94. /// A root game object to support correct navigation in local multi-player UIs.
  95. /// <remarks>
  96. /// In local multi-player games where each player has their own UI, players should not be able to navigate into
  97. /// another player's UI. Each player should have their own instance of an InputSystemUIInputModule, and this property
  98. /// should be set to the root game object containing all UI objects for that player. If set, navigation using the
  99. /// <see cref="InputSystemUIInputModule.move"/> action will be constrained to UI objects under that root.
  100. /// </remarks>
  101. /// </summary>
  102. internal GameObject localMultiPlayerRoot
  103. {
  104. get => m_LocalMultiPlayerRoot;
  105. set => m_LocalMultiPlayerRoot = value;
  106. }
  107. /// <summary>
  108. /// Called by <c>EventSystem</c> when the input module is made current.
  109. /// </summary>
  110. public override void ActivateModule()
  111. {
  112. base.ActivateModule();
  113. // Select firstSelectedGameObject if nothing is selected ATM.
  114. var toSelect = eventSystem.currentSelectedGameObject;
  115. if (toSelect == null)
  116. toSelect = eventSystem.firstSelectedGameObject;
  117. eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
  118. }
  119. /// <summary>
  120. /// Check whether the given pointer or touch is currently hovering over a <c>GameObject</c>.
  121. /// </summary>
  122. /// <param name="pointerOrTouchId">ID of the pointer or touch. Meaning this should correspond to either
  123. /// <c>PointerEventData.pointerId</c> or <see cref="ExtendedPointerEventData.touchId"/>. The pointer ID
  124. /// generally corresponds to the <see cref="InputDevice.deviceId"/> of the pointer device. An exception
  125. /// to this are touches as a <see cref="Touchscreen"/> may have multiple pointers (one for each active
  126. /// finger). For touch, you can use the <see cref="TouchControl.touchId"/> of the touch.
  127. ///
  128. /// Note that for touch, a pointer will stay valid for one frame before being removed. In other words,
  129. /// when <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/> is received for a touch
  130. /// and the touch was over a <c>GameObject</c>, the associated pointer is still considered over that
  131. /// object for the frame in which the touch ended.
  132. ///
  133. /// To check whether any pointer is over a <c>GameObject</c>, simply pass a negative value such as -1.</param>
  134. /// <returns>True if the given pointer is currently hovering over a <c>GameObject</c>.</returns>
  135. /// <remarks>
  136. /// The result is true if the given pointer has caused an <c>IPointerEnter</c> event to be sent to a
  137. /// <c>GameObject</c>.
  138. ///
  139. /// This method can be invoked via <c>EventSystem.current.IsPointerOverGameObject</c>.
  140. ///
  141. /// Be aware that this method relies on state set up during UI event processing that happens in <c>EventSystem.Update</c>,
  142. /// that is, as part of <c>MonoBehaviour</c> updates. This step happens <em>after</em> input processing.
  143. /// Thus, calling this method earlier than that in the frame will make it poll state from <em>last</em> frame.
  144. ///
  145. /// Calling this method from within an <see cref="InputAction"/> callback (such as <see cref="InputAction.performed"/>)
  146. /// will result in a warning. See the "UI vs Game Input" sample shipped with the Input System package for
  147. /// how to deal with this fact.
  148. ///
  149. /// <example>
  150. /// <code>
  151. /// // In general, the pointer ID corresponds to the device ID:
  152. /// EventSystem.current.IsPointerOverGameObject(XRController.leftHand.deviceId);
  153. /// EventSystem.current.IsPointerOverGameObject(Mouse.current.deviceId);
  154. ///
  155. /// // For touch input, pass the ID of a touch:
  156. /// EventSystem.current.IsPointerOverGameObject(Touchscreen.primaryTouch.touchId.ReadValue());
  157. ///
  158. /// // But can also pass the ID of the entire Touchscreen in which case the result
  159. /// // is true if any touch is over a GameObject:
  160. /// EventSystem.current.IsPointerOverGameObject(Touchscreen.current.deviceId);
  161. ///
  162. /// // Finally, any negative value will be interpreted as "any pointer" and will
  163. /// // return true if any one pointer is currently over a GameObject:
  164. /// EventSystem.current.IsPointerOverGameObject(-1);
  165. /// EventSystem.current.IsPointerOverGameObject(); // Equivalent.
  166. /// </code>
  167. /// </example>
  168. /// </remarks>
  169. /// <seealso cref="ExtendedPointerEventData.touchId"/>
  170. /// <seealso cref="InputDevice.deviceId"/>
  171. public override bool IsPointerOverGameObject(int pointerOrTouchId)
  172. {
  173. if (InputSystem.isProcessingEvents)
  174. Debug.LogWarning(
  175. "Calling IsPointerOverGameObject() from within event processing (such as from InputAction callbacks) will not work as expected; it will query UI state from the last frame");
  176. var stateIndex = -1;
  177. if (pointerOrTouchId < 0)
  178. {
  179. if (m_CurrentPointerId != -1)
  180. {
  181. stateIndex = m_CurrentPointerIndex;
  182. }
  183. else
  184. {
  185. // No current pointer. Can happen, for example, when a touch just ended and its pointer record
  186. // was removed as a result. If we still have some active pointer, use it.
  187. if (m_PointerStates.length > 0)
  188. stateIndex = 0;
  189. }
  190. }
  191. else
  192. {
  193. stateIndex = GetPointerStateIndexFor(pointerOrTouchId);
  194. }
  195. if (stateIndex == -1)
  196. return false;
  197. return m_PointerStates[stateIndex].eventData.pointerEnter != null;
  198. }
  199. /// <summary>
  200. /// Returns the most recent raycast information for a given pointer or touch.
  201. /// </summary>
  202. /// <param name="pointerOrTouchId">ID of the pointer or touch. Meaning this should correspond to either
  203. /// <c>PointerEventData.pointerId</c> or <see cref="ExtendedPointerEventData.touchId"/>. The pointer ID
  204. /// generally corresponds to the <see cref="InputDevice.deviceId"/> of the pointer device. An exception
  205. /// to this are touches as a <see cref="Touchscreen"/> may have multiple pointers (one for each active
  206. /// finger). For touch, you can use the <see cref="TouchControl.touchId"/> of the touch.
  207. ///
  208. /// Negative values will return an invalid <see cref="RaycastResult"/>.</param>
  209. /// <returns>The most recent raycast information.</returns>
  210. /// <remarks>
  211. /// This method is for the most recent raycast, but depending on when it's called is not guaranteed to be for the current frame.
  212. /// This method can be used to determine raycast distances and hit information for visualization.
  213. /// <br />
  214. /// Use <see cref="RaycastResult.isValid"/> to determine if pointer hit anything.
  215. /// </remarks>
  216. /// <seealso cref="ExtendedPointerEventData.touchId"/>
  217. /// <seealso cref="InputDevice.deviceId"/>
  218. public RaycastResult GetLastRaycastResult(int pointerOrTouchId)
  219. {
  220. var stateIndex = GetPointerStateIndexFor(pointerOrTouchId);
  221. if (stateIndex == -1)
  222. return default;
  223. return m_PointerStates[stateIndex].eventData.pointerCurrentRaycast;
  224. }
  225. private RaycastResult PerformRaycast(ExtendedPointerEventData eventData)
  226. {
  227. if (eventData == null)
  228. throw new ArgumentNullException(nameof(eventData));
  229. // If it's an event from a tracked device, see if we have a TrackedDeviceRaycaster and give it
  230. // the first shot.
  231. if (eventData.pointerType == UIPointerType.Tracked && TrackedDeviceRaycaster.s_Instances.length > 0)
  232. {
  233. for (var i = 0; i < TrackedDeviceRaycaster.s_Instances.length; ++i)
  234. {
  235. var trackedDeviceRaycaster = TrackedDeviceRaycaster.s_Instances[i];
  236. m_RaycastResultCache.Clear();
  237. trackedDeviceRaycaster.PerformRaycast(eventData, m_RaycastResultCache);
  238. if (m_RaycastResultCache.Count > 0)
  239. {
  240. var raycastResult = m_RaycastResultCache[0];
  241. m_RaycastResultCache.Clear();
  242. return raycastResult;
  243. }
  244. }
  245. return default;
  246. }
  247. // Otherwise pass it along to the normal raycasting logic.
  248. eventSystem.RaycastAll(eventData, m_RaycastResultCache);
  249. var result = FindFirstRaycast(m_RaycastResultCache);
  250. m_RaycastResultCache.Clear();
  251. return result;
  252. }
  253. // Mouse, pen, touch, and tracked device pointer input all go through here.
  254. private void ProcessPointer(ref PointerModel state)
  255. {
  256. var eventData = state.eventData;
  257. // Sync position.
  258. var pointerType = eventData.pointerType;
  259. if (pointerType == UIPointerType.MouseOrPen && Cursor.lockState == CursorLockMode.Locked)
  260. {
  261. eventData.position = m_CursorLockBehavior == CursorLockBehavior.OutsideScreen ?
  262. new Vector2(-1, -1) :
  263. new Vector2(Screen.width / 2f, Screen.height / 2f);
  264. ////REVIEW: This is consistent with StandaloneInputModule but having no deltas in locked mode seems wrong
  265. eventData.delta = default;
  266. }
  267. else if (pointerType == UIPointerType.Tracked)
  268. {
  269. var position = state.worldPosition;
  270. var rotation = state.worldOrientation;
  271. if (m_XRTrackingOrigin != null)
  272. {
  273. position = m_XRTrackingOrigin.TransformPoint(position);
  274. rotation = m_XRTrackingOrigin.rotation * rotation;
  275. }
  276. eventData.trackedDeviceOrientation = rotation;
  277. eventData.trackedDevicePosition = position;
  278. }
  279. else
  280. {
  281. eventData.delta = state.screenPosition - eventData.position;
  282. eventData.position = state.screenPosition;
  283. }
  284. // Clear the 'used' flag.
  285. eventData.Reset();
  286. // Raycast from current position.
  287. eventData.pointerCurrentRaycast = PerformRaycast(eventData);
  288. // Sync position for tracking devices. For those, we can only do this
  289. // after the raycast as the screen-space position is a byproduct of the raycast.
  290. if (pointerType == UIPointerType.Tracked && eventData.pointerCurrentRaycast.isValid)
  291. {
  292. var screenPos = eventData.pointerCurrentRaycast.screenPosition;
  293. eventData.delta = screenPos - eventData.position;
  294. eventData.position = eventData.pointerCurrentRaycast.screenPosition;
  295. }
  296. ////REVIEW: for touch, we only need the left button; should we skip right and middle button processing? then we also don't need to copy to/from the event
  297. // Left mouse button. Movement and scrolling is processed with event set left button.
  298. eventData.button = PointerEventData.InputButton.Left;
  299. state.leftButton.CopyPressStateTo(eventData);
  300. // Unlike StandaloneInputModule, we process moves before processing buttons. This way
  301. // UI elements get pointer enters/exits before they get button ups/downs and clicks.
  302. ProcessPointerMovement(ref state, eventData);
  303. // We always need to process move-related events in order to get PointerEnter and Exit events
  304. // when we change UI state (e.g. show/hide objects) without moving the pointer. This unfortunately
  305. // also means that we will invariably raycast on every update.
  306. // However, after that, early out at this point when there's no changes to the pointer state (except
  307. // for tracked pointers as the tracking origin may have moved).
  308. if (!state.changedThisFrame && (xrTrackingOrigin == null || state.pointerType != UIPointerType.Tracked))
  309. return;
  310. ProcessPointerButton(ref state.leftButton, eventData);
  311. ProcessPointerButtonDrag(ref state.leftButton, eventData);
  312. ProcessPointerScroll(ref state, eventData);
  313. // Right mouse button.
  314. eventData.button = PointerEventData.InputButton.Right;
  315. state.rightButton.CopyPressStateTo(eventData);
  316. ProcessPointerButton(ref state.rightButton, eventData);
  317. ProcessPointerButtonDrag(ref state.rightButton, eventData);
  318. // Middle mouse button.
  319. eventData.button = PointerEventData.InputButton.Middle;
  320. state.middleButton.CopyPressStateTo(eventData);
  321. ProcessPointerButton(ref state.middleButton, eventData);
  322. ProcessPointerButtonDrag(ref state.middleButton, eventData);
  323. }
  324. // if we are using a MultiplayerEventSystem, ignore any transforms
  325. // not under the current MultiplayerEventSystem's root.
  326. private bool PointerShouldIgnoreTransform(Transform t)
  327. {
  328. if (eventSystem is MultiplayerEventSystem multiplayerEventSystem && multiplayerEventSystem.playerRoot != null)
  329. {
  330. if (!t.IsChildOf(multiplayerEventSystem.playerRoot.transform))
  331. return true;
  332. }
  333. return false;
  334. }
  335. private void ProcessPointerMovement(ref PointerModel pointer, ExtendedPointerEventData eventData)
  336. {
  337. var currentPointerTarget =
  338. // If the pointer is a touch that was released the *previous* frame, we generate pointer-exit events
  339. // and then later remove the pointer.
  340. eventData.pointerType == UIPointerType.Touch && !pointer.leftButton.isPressed && !pointer.leftButton.wasReleasedThisFrame
  341. ? null
  342. : eventData.pointerCurrentRaycast.gameObject;
  343. ProcessPointerMovement(eventData, currentPointerTarget);
  344. }
  345. private void ProcessPointerMovement(ExtendedPointerEventData eventData, GameObject currentPointerTarget)
  346. {
  347. #if UNITY_2021_1_OR_NEWER
  348. // If the pointer moved, send move events to all UI elements the pointer is
  349. // currently over.
  350. var wasMoved = eventData.IsPointerMoving();
  351. if (wasMoved)
  352. {
  353. for (var i = 0; i < eventData.hovered.Count; ++i)
  354. ExecuteEvents.Execute(eventData.hovered[i], eventData, ExecuteEvents.pointerMoveHandler);
  355. }
  356. #endif
  357. // If we have no target or pointerEnter has been deleted,
  358. // we just send exit events to anything we are tracking
  359. // and then exit.
  360. if (currentPointerTarget == null || eventData.pointerEnter == null)
  361. {
  362. for (var i = 0; i < eventData.hovered.Count; ++i)
  363. ExecuteEvents.Execute(eventData.hovered[i], eventData, ExecuteEvents.pointerExitHandler);
  364. eventData.hovered.Clear();
  365. if (currentPointerTarget == null)
  366. {
  367. eventData.pointerEnter = null;
  368. return;
  369. }
  370. }
  371. if (eventData.pointerEnter == currentPointerTarget && currentPointerTarget)
  372. return;
  373. var commonRoot = FindCommonRoot(eventData.pointerEnter, currentPointerTarget)?.transform;
  374. // We walk up the tree until a common root and the last entered and current entered object is found.
  375. // Then send exit and enter events up to, but not including, the common root.
  376. if (eventData.pointerEnter != null)
  377. {
  378. for (var current = eventData.pointerEnter.transform; current != null && current != commonRoot; current = current.parent)
  379. {
  380. ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerExitHandler);
  381. eventData.hovered.Remove(current.gameObject);
  382. }
  383. }
  384. eventData.pointerEnter = currentPointerTarget;
  385. if (currentPointerTarget != null)
  386. {
  387. for (var current = currentPointerTarget.transform;
  388. current != null && current != commonRoot && !PointerShouldIgnoreTransform(current);
  389. current = current.parent)
  390. {
  391. ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerEnterHandler);
  392. #if UNITY_2021_1_OR_NEWER
  393. if (wasMoved)
  394. ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerMoveHandler);
  395. #endif
  396. eventData.hovered.Add(current.gameObject);
  397. }
  398. }
  399. }
  400. private const float kClickSpeed = 0.3f;
  401. private void ProcessPointerButton(ref PointerModel.ButtonState button, PointerEventData eventData)
  402. {
  403. var currentOverGo = eventData.pointerCurrentRaycast.gameObject;
  404. if (currentOverGo != null && PointerShouldIgnoreTransform(currentOverGo.transform))
  405. return;
  406. // Button press.
  407. if (button.wasPressedThisFrame)
  408. {
  409. button.pressTime = InputRuntime.s_Instance.unscaledGameTime;
  410. eventData.delta = Vector2.zero;
  411. eventData.dragging = false;
  412. eventData.pressPosition = eventData.position;
  413. eventData.pointerPressRaycast = eventData.pointerCurrentRaycast;
  414. eventData.eligibleForClick = true;
  415. eventData.useDragThreshold = true;
  416. var selectHandler = ExecuteEvents.GetEventHandler<ISelectHandler>(currentOverGo);
  417. // If we have clicked something new, deselect the old thing and leave 'selection handling' up
  418. // to the press event (except if there's none and we're told to not deselect in that case).
  419. if (selectHandler != eventSystem.currentSelectedGameObject && (selectHandler != null || m_DeselectOnBackgroundClick))
  420. eventSystem.SetSelectedGameObject(null, eventData);
  421. // Invoke OnPointerDown, if present.
  422. var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, eventData, ExecuteEvents.pointerDownHandler);
  423. // If no GO responded to OnPointerDown, look for one that responds to OnPointerClick.
  424. // NOTE: This only looks up the handler. We don't invoke OnPointerClick here.
  425. if (newPressed == null)
  426. newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
  427. // Reset click state if delay to last release was too long or if we didn't
  428. // press on the same object as last time. The latter part we don't know until
  429. // we've actually run the press handler.
  430. button.clickedOnSameGameObject = newPressed == eventData.lastPress && button.pressTime - eventData.clickTime <= kClickSpeed;
  431. if (eventData.clickCount > 0 && !button.clickedOnSameGameObject)
  432. {
  433. eventData.clickCount = default;
  434. eventData.clickTime = default;
  435. }
  436. // Set pointerPress. This nukes lastPress. Meaning that after OnPointerDown, lastPress will
  437. // become null.
  438. eventData.pointerPress = newPressed;
  439. eventData.rawPointerPress = currentOverGo;
  440. // Save the drag handler for drag events during this mouse down.
  441. eventData.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
  442. if (eventData.pointerDrag != null)
  443. ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.initializePotentialDrag);
  444. }
  445. // Button release.
  446. if (button.wasReleasedThisFrame)
  447. {
  448. // Check for click. Release must be on same GO that we pressed on and we must not
  449. // have moved beyond our move tolerance (doing so will set eligibleForClick to false).
  450. // NOTE: There's two difference to click handling here compared to StandaloneInputModule.
  451. // 1) StandaloneInputModule counts clicks entirely on press meaning that clickCount is increased
  452. // before a click has actually happened.
  453. // 2) StandaloneInputModule increases click counts even if something is eventually not deemed a
  454. // click and OnPointerClick is thus never invoked.
  455. var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
  456. var isClick = eventData.pointerPress == pointerClickHandler && eventData.eligibleForClick;
  457. if (isClick)
  458. {
  459. // Count clicks.
  460. if (button.clickedOnSameGameObject)
  461. {
  462. // We re-clicked on the same UI element within 0.3 seconds so count
  463. // it as a repeat click.
  464. ++eventData.clickCount;
  465. }
  466. else
  467. {
  468. // First click on this object.
  469. eventData.clickCount = 1;
  470. }
  471. eventData.clickTime = InputRuntime.s_Instance.unscaledGameTime;
  472. }
  473. // Invoke OnPointerUp.
  474. ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerUpHandler);
  475. // Invoke OnPointerClick or OnDrop.
  476. if (isClick)
  477. ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerClickHandler);
  478. else if (eventData.dragging && eventData.pointerDrag != null)
  479. ExecuteEvents.ExecuteHierarchy(currentOverGo, eventData, ExecuteEvents.dropHandler);
  480. eventData.eligibleForClick = false;
  481. eventData.pointerPress = null;
  482. eventData.rawPointerPress = null;
  483. if (eventData.dragging && eventData.pointerDrag != null)
  484. ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.endDragHandler);
  485. eventData.dragging = false;
  486. eventData.pointerDrag = null;
  487. button.ignoreNextClick = false;
  488. }
  489. button.CopyPressStateFrom(eventData);
  490. }
  491. private void ProcessPointerButtonDrag(ref PointerModel.ButtonState button, ExtendedPointerEventData eventData)
  492. {
  493. if (!eventData.IsPointerMoving() ||
  494. (eventData.pointerType == UIPointerType.MouseOrPen && Cursor.lockState == CursorLockMode.Locked) ||
  495. eventData.pointerDrag == null)
  496. return;
  497. // Detect drags.
  498. if (!eventData.dragging)
  499. {
  500. if (!eventData.useDragThreshold || (eventData.pressPosition - eventData.position).sqrMagnitude >=
  501. (double)eventSystem.pixelDragThreshold * eventSystem.pixelDragThreshold * (eventData.pointerType == UIPointerType.Tracked
  502. ? m_TrackedDeviceDragThresholdMultiplier
  503. : 1))
  504. {
  505. // Started dragging. Invoke OnBeginDrag.
  506. ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.beginDragHandler);
  507. eventData.dragging = true;
  508. }
  509. }
  510. if (eventData.dragging)
  511. {
  512. // If we moved from our initial press object, process an up for that object.
  513. if (eventData.pointerPress != eventData.pointerDrag)
  514. {
  515. ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerUpHandler);
  516. eventData.eligibleForClick = false;
  517. eventData.pointerPress = null;
  518. eventData.rawPointerPress = null;
  519. }
  520. ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.dragHandler);
  521. button.CopyPressStateFrom(eventData);
  522. }
  523. }
  524. private static void ProcessPointerScroll(ref PointerModel pointer, PointerEventData eventData)
  525. {
  526. var scrollDelta = pointer.scrollDelta;
  527. if (!Mathf.Approximately(scrollDelta.sqrMagnitude, 0.0f))
  528. {
  529. eventData.scrollDelta = scrollDelta;
  530. var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(eventData.pointerEnter);
  531. ExecuteEvents.ExecuteHierarchy(scrollHandler, eventData, ExecuteEvents.scrollHandler);
  532. }
  533. }
  534. internal void ProcessNavigation(ref NavigationModel navigationState)
  535. {
  536. var usedSelectionChange = false;
  537. if (eventSystem.currentSelectedGameObject != null)
  538. {
  539. var data = GetBaseEventData();
  540. ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);
  541. usedSelectionChange = data.used;
  542. }
  543. // Don't send move events if disabled in the EventSystem.
  544. if (!eventSystem.sendNavigationEvents)
  545. return;
  546. // Process move.
  547. var movement = navigationState.move;
  548. if (!usedSelectionChange && (!Mathf.Approximately(movement.x, 0f) || !Mathf.Approximately(movement.y, 0f)))
  549. {
  550. var time = InputRuntime.s_Instance.unscaledGameTime;
  551. var moveVector = navigationState.move;
  552. var moveDirection = MoveDirection.None;
  553. if (moveVector.sqrMagnitude > 0)
  554. {
  555. if (Mathf.Abs(moveVector.x) > Mathf.Abs(moveVector.y))
  556. moveDirection = moveVector.x > 0 ? MoveDirection.Right : MoveDirection.Left;
  557. else
  558. moveDirection = moveVector.y > 0 ? MoveDirection.Up : MoveDirection.Down;
  559. }
  560. ////REVIEW: is resetting move repeats when direction changes really useful behavior?
  561. if (moveDirection != m_NavigationState.lastMoveDirection)
  562. m_NavigationState.consecutiveMoveCount = 0;
  563. if (moveDirection != MoveDirection.None)
  564. {
  565. var allow = true;
  566. if (m_NavigationState.consecutiveMoveCount != 0)
  567. {
  568. if (m_NavigationState.consecutiveMoveCount > 1)
  569. allow = time > m_NavigationState.lastMoveTime + moveRepeatRate;
  570. else
  571. allow = time > m_NavigationState.lastMoveTime + moveRepeatDelay;
  572. }
  573. if (allow)
  574. {
  575. var eventData = m_NavigationState.eventData;
  576. if (eventData == null)
  577. {
  578. eventData = new ExtendedAxisEventData(eventSystem);
  579. m_NavigationState.eventData = eventData;
  580. }
  581. eventData.Reset();
  582. eventData.moveVector = moveVector;
  583. eventData.moveDir = moveDirection;
  584. if (IsMoveAllowed(eventData))
  585. {
  586. ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, eventData, ExecuteEvents.moveHandler);
  587. usedSelectionChange = eventData.used;
  588. m_NavigationState.consecutiveMoveCount = m_NavigationState.consecutiveMoveCount + 1;
  589. m_NavigationState.lastMoveTime = time;
  590. m_NavigationState.lastMoveDirection = moveDirection;
  591. }
  592. }
  593. }
  594. else
  595. m_NavigationState.consecutiveMoveCount = 0;
  596. }
  597. else
  598. {
  599. m_NavigationState.consecutiveMoveCount = 0;
  600. }
  601. // Process submit and cancel events.
  602. if (!usedSelectionChange && eventSystem.currentSelectedGameObject != null)
  603. {
  604. // NOTE: Whereas we use callbacks for the other actions, we rely on WasPressedThisFrame() for
  605. // submit and cancel. This makes their behavior inconsistent with pointer click behavior where
  606. // a click will register on button *up*, but consistent with how other UI systems work where
  607. // click occurs on key press. This nuance in behavior becomes important in combination with
  608. // action enable/disable changes in response to submit or cancel. We react to button *down*
  609. // instead of *up*, so button *up* will come in *after* we have applied the state change.
  610. var submitAction = m_SubmitAction?.action;
  611. var cancelAction = m_CancelAction?.action;
  612. var data = GetBaseEventData();
  613. if (cancelAction != null && cancelAction.WasPressedThisFrame())
  614. ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler);
  615. if (!data.used && submitAction != null && submitAction.WasPressedThisFrame())
  616. ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler);
  617. }
  618. }
  619. private bool IsMoveAllowed(AxisEventData eventData)
  620. {
  621. if (m_LocalMultiPlayerRoot == null)
  622. return true;
  623. if (eventSystem.currentSelectedGameObject == null)
  624. return true;
  625. var selectable = eventSystem.currentSelectedGameObject.GetComponent<Selectable>();
  626. if (selectable == null)
  627. return true;
  628. Selectable navigationTarget = null;
  629. switch (eventData.moveDir)
  630. {
  631. case MoveDirection.Right:
  632. navigationTarget = selectable.FindSelectableOnRight();
  633. break;
  634. case MoveDirection.Up:
  635. navigationTarget = selectable.FindSelectableOnUp();
  636. break;
  637. case MoveDirection.Left:
  638. navigationTarget = selectable.FindSelectableOnLeft();
  639. break;
  640. case MoveDirection.Down:
  641. navigationTarget = selectable.FindSelectableOnDown();
  642. break;
  643. }
  644. if (navigationTarget == null)
  645. return true;
  646. return navigationTarget.transform.IsChildOf(m_LocalMultiPlayerRoot.transform);
  647. }
  648. [FormerlySerializedAs("m_RepeatDelay")]
  649. [Tooltip("The Initial delay (in seconds) between an initial move action and a repeated move action.")]
  650. [SerializeField]
  651. private float m_MoveRepeatDelay = 0.5f;
  652. [FormerlySerializedAs("m_RepeatRate")]
  653. [Tooltip("The speed (in seconds) that the move action repeats itself once repeating (max 1 per frame).")]
  654. [SerializeField]
  655. private float m_MoveRepeatRate = 0.1f;
  656. [Tooltip("Scales the Eventsystem.DragThreshold, for tracked devices, to make selection easier.")]
  657. // Hide this while we still have to figure out what to do with this.
  658. private float m_TrackedDeviceDragThresholdMultiplier = 2.0f;
  659. [Tooltip("Transform representing the real world origin for tracking devices. When using the XR Interaction Toolkit, this should be pointing to the XR Rig's Transform.")]
  660. [SerializeField]
  661. private Transform m_XRTrackingOrigin;
  662. /// <summary>
  663. /// Delay in seconds between an initial move action and a repeated move action while <see cref="move"/> is actuated.
  664. /// </summary>
  665. /// <remarks>
  666. /// While <see cref="move"/> is being held down, the input module will first wait for <see cref="moveRepeatDelay"/> seconds
  667. /// after the first actuation of <see cref="move"/> and then trigger a move event every <see cref="moveRepeatRate"/> seconds.
  668. /// </remarks>
  669. /// <seealso cref="moveRepeatRate"/>
  670. /// <seealso cref="AxisEventData"/>
  671. /// <see cref="move"/>
  672. public float moveRepeatDelay
  673. {
  674. get => m_MoveRepeatDelay;
  675. set => m_MoveRepeatDelay = value;
  676. }
  677. /// <summary>
  678. /// Delay in seconds between repeated move actions while <see cref="move"/> is actuated.
  679. /// </summary>
  680. /// <remarks>
  681. /// While <see cref="move"/> is being held down, the input module will first wait for <see cref="moveRepeatDelay"/> seconds
  682. /// after the first actuation of <see cref="move"/> and then trigger a move event every <see cref="moveRepeatRate"/> seconds.
  683. ///
  684. /// Note that a maximum of one <see cref="AxisEventData"/> will be sent per frame. This means that even if multiple time
  685. /// increments of the repeat delay have passed since the last update, only one move repeat event will be generated.
  686. /// </remarks>
  687. /// <seealso cref="moveRepeatDelay"/>
  688. /// <seealso cref="AxisEventData"/>
  689. /// <see cref="move"/>
  690. public float moveRepeatRate
  691. {
  692. get => m_MoveRepeatRate;
  693. set => m_MoveRepeatRate = value;
  694. }
  695. private bool explictlyIgnoreFocus => InputSystem.settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus;
  696. private bool shouldIgnoreFocus
  697. {
  698. // By default, key this on whether running the background is enabled or not. Rationale is that
  699. // if running in the background is enabled, we already have rules in place what kind of input
  700. // is allowed through and what isn't. And for the input that *IS* allowed through, the UI should
  701. // react.
  702. get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground;
  703. }
  704. [Obsolete("'repeatRate' has been obsoleted; use 'moveRepeatRate' instead. (UnityUpgradable) -> moveRepeatRate", false)]
  705. public float repeatRate
  706. {
  707. get => moveRepeatRate;
  708. set => moveRepeatRate = value;
  709. }
  710. [Obsolete("'repeatDelay' has been obsoleted; use 'moveRepeatDelay' instead. (UnityUpgradable) -> moveRepeatDelay", false)]
  711. public float repeatDelay
  712. {
  713. get => moveRepeatDelay;
  714. set => moveRepeatDelay = value;
  715. }
  716. /// <summary>
  717. /// A <see cref="Transform"/> representing the real world origin for tracking devices.
  718. /// This is used to convert real world positions and rotations for <see cref="UIPointerType.Tracked"/> pointers into Unity's global space.
  719. /// When using the XR Interaction Toolkit, this should be pointing to the XR Rig's Transform.
  720. /// </summary>
  721. /// <remarks>This will transform all tracked pointers. If unset, or set to null, the Unity world origin will be used as the basis for all tracked positions and rotations.</remarks>
  722. public Transform xrTrackingOrigin
  723. {
  724. get => m_XRTrackingOrigin;
  725. set => m_XRTrackingOrigin = value;
  726. }
  727. /// <summary>
  728. /// Scales the drag threshold of <c>EventSystem</c> for tracked devices to make selection easier.
  729. /// </summary>
  730. public float trackedDeviceDragThresholdMultiplier
  731. {
  732. get => m_TrackedDeviceDragThresholdMultiplier;
  733. set => m_TrackedDeviceDragThresholdMultiplier = value;
  734. }
  735. private void SwapAction(ref InputActionReference property, InputActionReference newValue, bool actionsHooked, Action<InputAction.CallbackContext> actionCallback)
  736. {
  737. if (property == newValue || (property != null && newValue != null && property.action == newValue.action))
  738. return;
  739. if (property != null && actionCallback != null && actionsHooked)
  740. {
  741. property.action.performed -= actionCallback;
  742. property.action.canceled -= actionCallback;
  743. }
  744. var oldActionNull = property?.action == null;
  745. var oldActionEnabled = property?.action != null && property.action.enabled;
  746. TryDisableInputAction(property);
  747. property = newValue;
  748. #if DEBUG
  749. // We source inputs from arbitrary pointers through a set of pointer-related actions (point, click, etc). This means that in any frame,
  750. // multiple pointers may pipe input through to the same action and we do not want the disambiguation code in InputActionState.ShouldIgnoreControlStateChange()
  751. // to prevent input from getting to us. Thus, these actions should generally be set to InputActionType.PassThrough.
  752. //
  753. // We treat navigation actions differently as there is only a single NavigationModel for the UI that all navigation input feeds into.
  754. // Thus, those actions should be configured with disambiguation active (i.e. Move should be a Value action and Submit and Cancel should
  755. // be Button actions). This is especially important for Submit and Cancel as we get proper press and release action this way.
  756. if (newValue != null && newValue.action != null && newValue.action.type != InputActionType.PassThrough && !IsNavigationAction(newValue))
  757. {
  758. Debug.LogWarning("Pointer-related actions used with the UI input module should generally be set to Pass-Through type so that the module can properly distinguish between "
  759. + $"input from multiple pointers (action {newValue.action} is set to {newValue.action.type})", this);
  760. }
  761. #endif
  762. if (newValue?.action != null && actionCallback != null && actionsHooked)
  763. {
  764. property.action.performed += actionCallback;
  765. property.action.canceled += actionCallback;
  766. }
  767. if (isActiveAndEnabled && newValue?.action != null && (oldActionEnabled || oldActionNull))
  768. EnableInputAction(property);
  769. }
  770. #if DEBUG
  771. private bool IsNavigationAction(InputActionReference reference)
  772. {
  773. return reference == m_SubmitAction || reference == m_CancelAction || reference == m_MoveAction;
  774. }
  775. #endif
  776. /// <summary>
  777. /// An <see cref="InputAction"/> delivering a <see cref="Vector2"/> 2D screen position
  778. /// used as a cursor for pointing at UI elements.
  779. /// </summary>
  780. /// <remarks>
  781. /// The values read from this action determine <see cref="PointerEventData.position"/> and <see cref="PointerEventData.delta"/>.
  782. ///
  783. /// Together with <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
  784. /// <see cref="scrollWheel"/>, this forms the basis for pointer-type UI input.
  785. ///
  786. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  787. /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector2"</c>.
  788. ///
  789. /// <example>
  790. /// <code>
  791. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  792. /// var map = asset.AddActionMap("UI");
  793. /// var pointAction = map.AddAction("Point");
  794. ///
  795. /// pointAction.AddBinding("&lt;Mouse&gt;/position");
  796. /// pointAction.AddBinding("&lt;Touchscreen&gt;/touch*/position");
  797. ///
  798. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
  799. /// InputActionReference.Create(pointAction);
  800. /// </code>
  801. /// </example>
  802. /// </remarks>
  803. /// <seealso cref="leftClick"/>
  804. /// <seealso cref="rightClick"/>
  805. /// <seealso cref="middleClick"/>
  806. /// <seealso cref="scrollWheel"/>
  807. public InputActionReference point
  808. {
  809. get => m_PointAction;
  810. set => SwapAction(ref m_PointAction, value, m_ActionsHooked, m_OnPointDelegate);
  811. }
  812. /// <summary>
  813. /// An <see cref="InputAction"/> delivering a <c>Vector2</c> scroll wheel value
  814. /// used for sending <see cref="PointerEventData"/> events.
  815. /// </summary>
  816. /// <remarks>
  817. /// The values read from this action determine <see cref="PointerEventData.scrollDelta"/>.
  818. ///
  819. /// Together with <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
  820. /// <see cref="point"/>, this forms the basis for pointer-type UI input.
  821. ///
  822. /// Note that the action is optional. A pointer is fully functional with just <see cref="point"/>
  823. /// and <see cref="leftClick"/> alone.
  824. ///
  825. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  826. /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector2"</c>.
  827. ///
  828. /// <example>
  829. /// <code>
  830. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  831. /// var map = asset.AddActionMap("UI");
  832. /// var pointAction = map.AddAction("scroll");
  833. /// var scrollAction = map.AddAction("scroll");
  834. ///
  835. /// pointAction.AddBinding("&lt;Mouse&gt;/position");
  836. /// pointAction.AddBinding("&lt;Touchscreen&gt;/touch*/position");
  837. ///
  838. /// scrollAction.AddBinding("&lt;Mouse&gt;/scroll");
  839. ///
  840. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
  841. /// InputActionReference.Create(pointAction);
  842. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).scrollWheel =
  843. /// InputActionReference.Create(scrollAction);
  844. /// </code>
  845. /// </example>
  846. /// </remarks>
  847. /// <seealso cref="leftClick"/>
  848. /// <seealso cref="rightClick"/>
  849. /// <seealso cref="middleClick"/>
  850. /// <seealso cref="point"/>
  851. public InputActionReference scrollWheel
  852. {
  853. get => m_ScrollWheelAction;
  854. set => SwapAction(ref m_ScrollWheelAction, value, m_ActionsHooked, m_OnScrollWheelDelegate);
  855. }
  856. /// <summary>
  857. /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines
  858. /// whether the left button of a pointer is pressed.
  859. /// </summary>
  860. /// <remarks>
  861. /// Clicks on this button will use <see cref="PointerEventData.InputButton.Left"/> for <see cref="PointerEventData.button"/>.
  862. ///
  863. /// Together with <see cref="point"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
  864. /// <see cref="scrollWheel"/>, this forms the basis for pointer-type UI input.
  865. ///
  866. /// Note that together with <see cref="point"/>, this action is necessary for a pointer to be functional. The other clicks
  867. /// and <see cref="scrollWheel"/> are optional, however.
  868. ///
  869. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  870. /// <see cref="InputAction.expectedControlType"/> set to <c>"Button"</c>.
  871. ///
  872. /// <example>
  873. /// <code>
  874. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  875. /// var map = asset.AddActionMap("UI");
  876. /// var pointAction = map.AddAction("scroll");
  877. /// var clickAction = map.AddAction("click");
  878. ///
  879. /// pointAction.AddBinding("&lt;Mouse&gt;/position");
  880. /// pointAction.AddBinding("&lt;Touchscreen&gt;/touch*/position");
  881. ///
  882. /// clickAction.AddBinding("&lt;Mouse&gt;/leftButton");
  883. /// clickAction.AddBinding("&lt;Touchscreen&gt;/touch*/press");
  884. ///
  885. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
  886. /// InputActionReference.Create(pointAction);
  887. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
  888. /// InputActionReference.Create(clickAction);
  889. /// </code>
  890. /// </example>
  891. /// </remarks>
  892. /// <seealso cref="rightClick"/>
  893. /// <seealso cref="middleClick"/>
  894. /// <seealso cref="scrollWheel"/>
  895. /// <seealso cref="point"/>
  896. public InputActionReference leftClick
  897. {
  898. get => m_LeftClickAction;
  899. set => SwapAction(ref m_LeftClickAction, value, m_ActionsHooked, m_OnLeftClickDelegate);
  900. }
  901. /// <summary>
  902. /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines
  903. /// whether the middle button of a pointer is pressed.
  904. /// </summary>
  905. /// <remarks>
  906. /// Clicks on this button will use <see cref="PointerEventData.InputButton.Middle"/> for <see cref="PointerEventData.button"/>.
  907. ///
  908. /// Together with <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="scrollWheel"/>, and
  909. /// <see cref="point"/>, this forms the basis for pointer-type UI input.
  910. ///
  911. /// Note that the action is optional. A pointer is fully functional with just <see cref="point"/>
  912. /// and <see cref="leftClick"/> alone.
  913. ///
  914. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  915. /// <see cref="InputAction.expectedControlType"/> set to <c>"Button"</c>.
  916. ///
  917. /// <example>
  918. /// <code>
  919. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  920. /// var map = asset.AddActionMap("UI");
  921. /// var pointAction = map.AddAction("scroll");
  922. /// var leftClickAction = map.AddAction("leftClick");
  923. /// var middleClickAction = map.AddAction("middleClick");
  924. ///
  925. /// pointAction.AddBinding("&lt;Mouse&gt;/position");
  926. /// pointAction.AddBinding("&lt;Touchscreen&gt;/touch*/position");
  927. ///
  928. /// leftClickAction.AddBinding("&lt;Mouse&gt;/leftButton");
  929. /// leftClickAction.AddBinding("&lt;Touchscreen&gt;/touch*/press");
  930. ///
  931. /// middleClickAction.AddBinding("&lt;Mouse&gt;/middleButton");
  932. ///
  933. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
  934. /// InputActionReference.Create(pointAction);
  935. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
  936. /// InputActionReference.Create(leftClickAction);
  937. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).middleClick =
  938. /// InputActionReference.Create(middleClickAction);
  939. /// </code>
  940. /// </example>
  941. /// </remarks>
  942. /// <seealso cref="leftClick"/>
  943. /// <seealso cref="rightClick"/>
  944. /// <seealso cref="scrollWheel"/>
  945. /// <seealso cref="point"/>
  946. public InputActionReference middleClick
  947. {
  948. get => m_MiddleClickAction;
  949. set => SwapAction(ref m_MiddleClickAction, value, m_ActionsHooked, m_OnMiddleClickDelegate);
  950. }
  951. /// <summary>
  952. /// An <see cref="InputAction"/> delivering a <c>float"</c> button value that determines
  953. /// whether the right button of a pointer is pressed.
  954. /// </summary>
  955. /// <remarks>
  956. /// Clicks on this button will use <see cref="PointerEventData.InputButton.Right"/> for <see cref="PointerEventData.button"/>.
  957. ///
  958. /// Together with <see cref="leftClick"/>, <see cref="middleClick"/>, <see cref="scrollWheel"/>, and
  959. /// <see cref="point"/>, this forms the basis for pointer-type UI input.
  960. ///
  961. /// Note that the action is optional. A pointer is fully functional with just <see cref="point"/>
  962. /// and <see cref="leftClick"/> alone.
  963. ///
  964. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  965. /// <see cref="InputAction.expectedControlType"/> set to <c>"Button"</c>.
  966. ///
  967. /// <example>
  968. /// <code>
  969. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  970. /// var map = asset.AddActionMap("UI");
  971. /// var pointAction = map.AddAction("scroll");
  972. /// var leftClickAction = map.AddAction("leftClick");
  973. /// var rightClickAction = map.AddAction("rightClick");
  974. ///
  975. /// pointAction.AddBinding("&lt;Mouse&gt;/position");
  976. /// pointAction.AddBinding("&lt;Touchscreen&gt;/touch*/position");
  977. ///
  978. /// leftClickAction.AddBinding("&lt;Mouse&gt;/leftButton");
  979. /// leftClickAction.AddBinding("&lt;Touchscreen&gt;/touch*/press");
  980. ///
  981. /// rightClickAction.AddBinding("&lt;Mouse&gt;/rightButton");
  982. ///
  983. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
  984. /// InputActionReference.Create(pointAction);
  985. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
  986. /// InputActionReference.Create(leftClickAction);
  987. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).rightClick =
  988. /// InputActionReference.Create(rightClickAction);
  989. /// </code>
  990. /// </example>
  991. /// </remarks>
  992. /// <seealso cref="leftClick"/>
  993. /// <seealso cref="middleClick"/>
  994. /// <seealso cref="scrollWheel"/>
  995. /// <seealso cref="point"/>
  996. public InputActionReference rightClick
  997. {
  998. get => m_RightClickAction;
  999. set => SwapAction(ref m_RightClickAction, value, m_ActionsHooked, m_OnRightClickDelegate);
  1000. }
  1001. /// <summary>
  1002. /// An <see cref="InputAction"/> delivering a <c>Vector2</c> 2D motion vector
  1003. /// used for sending <see cref="AxisEventData"/> navigation events.
  1004. /// </summary>
  1005. /// <remarks>
  1006. /// The events generated from this input will be received by <see cref="IMoveHandler.OnMove"/>.
  1007. ///
  1008. /// This action together with <see cref="submit"/> and <see cref="cancel"/> form the sources for navigation-style
  1009. /// UI input.
  1010. ///
  1011. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  1012. /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector2"</c>.
  1013. ///
  1014. /// <example>
  1015. /// <code>
  1016. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  1017. /// var map = asset.AddActionMap("UI");
  1018. /// var pointAction = map.AddAction("move");
  1019. /// var submitAction = map.AddAction("submit");
  1020. /// var cancelAction = map.AddAction("cancel");
  1021. ///
  1022. /// moveAction.AddBinding("&lt;Gamepad&gt;/*stick");
  1023. /// moveAction.AddBinding("&lt;Gamepad&gt;/dpad");
  1024. /// submitAction.AddBinding("&lt;Gamepad&gt;/buttonSouth");
  1025. /// cancelAction.AddBinding("&lt;Gamepad&gt;/buttonEast");
  1026. ///
  1027. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).move =
  1028. /// InputActionReference.Create(moveAction);
  1029. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).submit =
  1030. /// InputActionReference.Create(submitAction);
  1031. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).cancelAction =
  1032. /// InputActionReference.Create(cancelAction);
  1033. /// </code>
  1034. /// </example>
  1035. /// </remarks>
  1036. /// <seealso cref="submit"/>
  1037. /// <seealso cref="cancel"/>
  1038. public InputActionReference move
  1039. {
  1040. get => m_MoveAction;
  1041. set => SwapAction(ref m_MoveAction, value, m_ActionsHooked, m_OnMoveDelegate);
  1042. }
  1043. /// <summary>
  1044. /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines when <c>ISubmitHandler</c>
  1045. /// is triggered.
  1046. /// </summary>
  1047. /// <remarks>
  1048. /// The events generated from this input will be received by <see cref="ISubmitHandler"/>.
  1049. ///
  1050. /// This action together with <see cref="move"/> and <see cref="cancel"/> form the sources for navigation-style
  1051. /// UI input.
  1052. ///
  1053. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.Button"/>.
  1054. ///
  1055. /// <example>
  1056. /// <code>
  1057. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  1058. /// var map = asset.AddActionMap("UI");
  1059. /// var pointAction = map.AddAction("move");
  1060. /// var submitAction = map.AddAction("submit");
  1061. /// var cancelAction = map.AddAction("cancel");
  1062. ///
  1063. /// moveAction.AddBinding("&lt;Gamepad&gt;/*stick");
  1064. /// moveAction.AddBinding("&lt;Gamepad&gt;/dpad");
  1065. /// submitAction.AddBinding("&lt;Gamepad&gt;/buttonSouth");
  1066. /// cancelAction.AddBinding("&lt;Gamepad&gt;/buttonEast");
  1067. ///
  1068. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).move =
  1069. /// InputActionReference.Create(moveAction);
  1070. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).submit =
  1071. /// InputActionReference.Create(submitAction);
  1072. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).cancelAction =
  1073. /// InputActionReference.Create(cancelAction);
  1074. /// </code>
  1075. /// </example>
  1076. /// </remarks>
  1077. /// <seealso cref="move"/>
  1078. /// <seealso cref="cancel"/>
  1079. public InputActionReference submit
  1080. {
  1081. get => m_SubmitAction;
  1082. set => SwapAction(ref m_SubmitAction, value, m_ActionsHooked, null);
  1083. }
  1084. /// <summary>
  1085. /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines when <c>ICancelHandler</c>
  1086. /// is triggered.
  1087. /// </summary>
  1088. /// <remarks>
  1089. /// The events generated from this input will be received by <see cref="ICancelHandler"/>.
  1090. ///
  1091. /// This action together with <see cref="move"/> and <see cref="submit"/> form the sources for navigation-style
  1092. /// UI input.
  1093. ///
  1094. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.Button"/>.
  1095. ///
  1096. /// <example>
  1097. /// <code>
  1098. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  1099. /// var map = asset.AddActionMap("UI");
  1100. /// var pointAction = map.AddAction("move");
  1101. /// var submitAction = map.AddAction("submit");
  1102. /// var cancelAction = map.AddAction("cancel");
  1103. ///
  1104. /// moveAction.AddBinding("&lt;Gamepad&gt;/*stick");
  1105. /// moveAction.AddBinding("&lt;Gamepad&gt;/dpad");
  1106. /// submitAction.AddBinding("&lt;Gamepad&gt;/buttonSouth");
  1107. /// cancelAction.AddBinding("&lt;Gamepad&gt;/buttonEast");
  1108. ///
  1109. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).move =
  1110. /// InputActionReference.Create(moveAction);
  1111. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).submit =
  1112. /// InputActionReference.Create(submitAction);
  1113. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).cancelAction =
  1114. /// InputActionReference.Create(cancelAction);
  1115. /// </code>
  1116. /// </example>
  1117. /// </remarks>
  1118. /// <seealso cref="move"/>
  1119. /// <seealso cref="submit"/>
  1120. public InputActionReference cancel
  1121. {
  1122. get => m_CancelAction;
  1123. set => SwapAction(ref m_CancelAction, value, m_ActionsHooked, null);
  1124. }
  1125. /// <summary>
  1126. /// An <see cref="InputAction"/> delivering a <c>Quaternion</c> value reflecting the orientation of <see cref="TrackedDevice"/>s.
  1127. /// In combination with <see cref="trackedDevicePosition"/>, this is used to determine the transform of tracked devices from which
  1128. /// to raycast into the UI scene.
  1129. /// </summary>
  1130. /// <remarks>
  1131. /// <see cref="trackedDeviceOrientation"/> and <see cref="trackedDevicePosition"/> together replace <see cref="point"/> for
  1132. /// UI input from <see cref="TrackedDevice"/>. Other than that, UI input for tracked devices is no different from "normal"
  1133. /// pointer-type input. This means that <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
  1134. /// <see cref="scrollWheel"/> can all be used for tracked device input like for regular pointer input.
  1135. ///
  1136. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  1137. /// <see cref="InputAction.expectedControlType"/> set to <c>"Quaternion"</c>.
  1138. ///
  1139. /// <example>
  1140. /// <code>
  1141. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  1142. /// var map = asset.AddActionMap("UI");
  1143. /// var positionAction = map.AddAction("position");
  1144. /// var orientationAction = map.AddAction("orientation");
  1145. /// var clickAction = map.AddAction("click");
  1146. ///
  1147. /// positionAction.AddBinding("&lt;TrackedDevice&gt;/devicePosition");
  1148. /// orientationAction.AddBinding("&lt;TrackedDevice&gt;/deviceRotation");
  1149. /// clickAction.AddBinding("&lt;TrackedDevice&gt;/trigger");
  1150. ///
  1151. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDevicePosition =
  1152. /// InputActionReference.Create(positionAction);
  1153. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDeviceOrientation =
  1154. /// InputActionReference.Create(orientationAction);
  1155. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
  1156. /// InputActionReference.Create(clickAction);
  1157. /// </code>
  1158. /// </example>
  1159. /// </remarks>
  1160. /// <seealso cref="trackedDevicePosition"/>
  1161. public InputActionReference trackedDeviceOrientation
  1162. {
  1163. get => m_TrackedDeviceOrientationAction;
  1164. set => SwapAction(ref m_TrackedDeviceOrientationAction, value, m_ActionsHooked, m_OnTrackedDeviceOrientationDelegate);
  1165. }
  1166. /// <summary>
  1167. /// An <see cref="InputAction"/> delivering a <c>Vector3</c> value reflecting the position of <see cref="TrackedDevice"/>s.
  1168. /// In combination with <see cref="trackedDeviceOrientation"/>, this is used to determine the transform of tracked devices from which
  1169. /// to raycast into the UI scene.
  1170. /// </summary>
  1171. /// <remarks>
  1172. /// <see cref="trackedDeviceOrientation"/> and <see cref="trackedDevicePosition"/> together replace <see cref="point"/> for
  1173. /// UI input from <see cref="TrackedDevice"/>. Other than that, UI input for tracked devices is no different from "normal"
  1174. /// pointer-type input. This means that <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
  1175. /// <see cref="scrollWheel"/> can all be used for tracked device input like for regular pointer input.
  1176. ///
  1177. /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
  1178. /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector3"</c>.
  1179. ///
  1180. /// <example>
  1181. /// <code>
  1182. /// var asset = ScriptableObject.Create&lt;InputActionAsset&gt;();
  1183. /// var map = asset.AddActionMap("UI");
  1184. /// var positionAction = map.AddAction("position");
  1185. /// var orientationAction = map.AddAction("orientation");
  1186. /// var clickAction = map.AddAction("click");
  1187. ///
  1188. /// positionAction.AddBinding("&lt;TrackedDevice&gt;/devicePosition");
  1189. /// orientationAction.AddBinding("&lt;TrackedDevice&gt;/deviceRotation");
  1190. /// clickAction.AddBinding("&lt;TrackedDevice&gt;/trigger");
  1191. ///
  1192. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDevicePosition =
  1193. /// InputActionReference.Create(positionAction);
  1194. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDeviceOrientation =
  1195. /// InputActionReference.Create(orientationAction);
  1196. /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
  1197. /// InputActionReference.Create(clickAction);
  1198. /// </code>
  1199. /// </example>
  1200. /// </remarks>
  1201. /// <seealso cref="trackedDeviceOrientation"/>
  1202. public InputActionReference trackedDevicePosition
  1203. {
  1204. get => m_TrackedDevicePositionAction;
  1205. set => SwapAction(ref m_TrackedDevicePositionAction, value, m_ActionsHooked, m_OnTrackedDevicePositionDelegate);
  1206. }
  1207. /// <summary>
  1208. /// Assigns default input actions asset and input actions, similar to how defaults are assigned when creating UI module in editor.
  1209. /// Useful for creating <see cref="InputSystemUIInputModule"/> at runtime.
  1210. /// </summary>
  1211. /// <remarks>
  1212. /// This instantiates <see cref="DefaultInputActions"/> and assigns it to <see cref="actionsAsset"/>. It also
  1213. /// assigns all the various individual actions such as <see cref="point"/> and <see cref="leftClick"/>.
  1214. ///
  1215. /// Note that if an <c>InputSystemUIInputModule</c> component is programmatically added to a <c>GameObject</c>,
  1216. /// it will automatically receive the default actions as part of its <c>OnEnable</c> method. Use <see cref="UnassignActions"/>
  1217. /// to remove these assignments.
  1218. ///
  1219. /// <example>
  1220. /// <code>
  1221. /// var go = new GameObject();
  1222. /// go.AddComponent&lt;EventSystem&gt;();
  1223. ///
  1224. /// // Adding the UI module like this will implicitly enable it and thus lead to
  1225. /// // automatic assignment of the default input actions.
  1226. /// var uiModule = go.AddComponent&lt;InputSystemUIInputModule&gt;();
  1227. ///
  1228. /// // Manually remove the default input actions.
  1229. /// uiModule.UnassignActions();
  1230. /// </code>
  1231. /// </example>
  1232. /// </remarks>
  1233. /// <seealso cref="actionsAsset"/>
  1234. /// <seealso cref="DefaultInputActions"/>
  1235. private static DefaultInputActions defaultActions;
  1236. public void AssignDefaultActions()
  1237. {
  1238. if (defaultActions == null)
  1239. {
  1240. defaultActions = new DefaultInputActions();
  1241. }
  1242. actionsAsset = defaultActions.asset;
  1243. cancel = InputActionReference.Create(defaultActions.UI.Cancel);
  1244. submit = InputActionReference.Create(defaultActions.UI.Submit);
  1245. move = InputActionReference.Create(defaultActions.UI.Navigate);
  1246. leftClick = InputActionReference.Create(defaultActions.UI.Click);
  1247. rightClick = InputActionReference.Create(defaultActions.UI.RightClick);
  1248. middleClick = InputActionReference.Create(defaultActions.UI.MiddleClick);
  1249. point = InputActionReference.Create(defaultActions.UI.Point);
  1250. scrollWheel = InputActionReference.Create(defaultActions.UI.ScrollWheel);
  1251. trackedDeviceOrientation = InputActionReference.Create(defaultActions.UI.TrackedDeviceOrientation);
  1252. trackedDevicePosition = InputActionReference.Create(defaultActions.UI.TrackedDevicePosition);
  1253. }
  1254. /// <summary>
  1255. /// Remove all action assignments, that is <see cref="actionsAsset"/> as well as all individual
  1256. /// actions such as <see cref="leftClick"/>.
  1257. /// </summary>
  1258. /// <remarks>
  1259. /// If the current actions were enabled by the UI input module, they will be disabled in the process.
  1260. /// </remarks>
  1261. /// <seealso cref="AssignDefaultActions"/>
  1262. public void UnassignActions()
  1263. {
  1264. defaultActions?.Dispose();
  1265. defaultActions = default;
  1266. actionsAsset = default;
  1267. cancel = default;
  1268. submit = default;
  1269. move = default;
  1270. leftClick = default;
  1271. rightClick = default;
  1272. middleClick = default;
  1273. point = default;
  1274. scrollWheel = default;
  1275. trackedDeviceOrientation = default;
  1276. trackedDevicePosition = default;
  1277. }
  1278. [Obsolete("'trackedDeviceSelect' has been obsoleted; use 'leftClick' instead.", true)]
  1279. public InputActionReference trackedDeviceSelect
  1280. {
  1281. get => throw new InvalidOperationException();
  1282. set => throw new InvalidOperationException();
  1283. }
  1284. #if UNITY_EDITOR
  1285. protected override void Reset()
  1286. {
  1287. base.Reset();
  1288. var asset = (InputActionAsset)AssetDatabase.LoadAssetAtPath(
  1289. UnityEngine.InputSystem.Editor.PlayerInputEditor.kDefaultInputActionsAssetPath,
  1290. typeof(InputActionAsset));
  1291. // Setting default asset and actions when creating via inspector
  1292. Editor.InputSystemUIInputModuleEditor.ReassignActions(this, asset);
  1293. }
  1294. #endif
  1295. protected override void Awake()
  1296. {
  1297. base.Awake();
  1298. m_NavigationState.Reset();
  1299. }
  1300. protected override void OnDestroy()
  1301. {
  1302. base.OnDestroy();
  1303. UnhookActions();
  1304. }
  1305. protected override void OnEnable()
  1306. {
  1307. base.OnEnable();
  1308. if (m_OnControlsChangedDelegate == null)
  1309. m_OnControlsChangedDelegate = OnControlsChanged;
  1310. InputActionState.s_GlobalState.onActionControlsChanged.AddCallback(m_OnControlsChangedDelegate);
  1311. if (HasNoActions())
  1312. AssignDefaultActions();
  1313. ResetPointers();
  1314. HookActions();
  1315. EnableAllActions();
  1316. }
  1317. protected override void OnDisable()
  1318. {
  1319. ResetPointers();
  1320. InputActionState.s_GlobalState.onActionControlsChanged.RemoveCallback(m_OnControlsChangedDelegate);
  1321. DisableAllActions();
  1322. UnhookActions();
  1323. base.OnDisable();
  1324. }
  1325. private void ResetPointers()
  1326. {
  1327. var numPointers = m_PointerStates.length;
  1328. for (var i = 0; i < numPointers; ++i)
  1329. SendPointerExitEventsAndRemovePointer(0);
  1330. m_CurrentPointerId = -1;
  1331. m_CurrentPointerIndex = -1;
  1332. m_CurrentPointerType = UIPointerType.None;
  1333. }
  1334. private bool HasNoActions()
  1335. {
  1336. if (m_ActionsAsset != null)
  1337. return false;
  1338. return m_PointAction?.action == null
  1339. && m_LeftClickAction?.action == null
  1340. && m_RightClickAction?.action == null
  1341. && m_MiddleClickAction?.action == null
  1342. && m_SubmitAction?.action == null
  1343. && m_CancelAction?.action == null
  1344. && m_ScrollWheelAction?.action == null
  1345. && m_TrackedDeviceOrientationAction?.action == null
  1346. && m_TrackedDevicePositionAction?.action == null;
  1347. }
  1348. private void EnableAllActions()
  1349. {
  1350. EnableInputAction(m_PointAction);
  1351. EnableInputAction(m_LeftClickAction);
  1352. EnableInputAction(m_RightClickAction);
  1353. EnableInputAction(m_MiddleClickAction);
  1354. EnableInputAction(m_MoveAction);
  1355. EnableInputAction(m_SubmitAction);
  1356. EnableInputAction(m_CancelAction);
  1357. EnableInputAction(m_ScrollWheelAction);
  1358. EnableInputAction(m_TrackedDeviceOrientationAction);
  1359. EnableInputAction(m_TrackedDevicePositionAction);
  1360. }
  1361. private void DisableAllActions()
  1362. {
  1363. TryDisableInputAction(m_PointAction, true);
  1364. TryDisableInputAction(m_LeftClickAction, true);
  1365. TryDisableInputAction(m_RightClickAction, true);
  1366. TryDisableInputAction(m_MiddleClickAction, true);
  1367. TryDisableInputAction(m_MoveAction, true);
  1368. TryDisableInputAction(m_SubmitAction, true);
  1369. TryDisableInputAction(m_CancelAction, true);
  1370. TryDisableInputAction(m_ScrollWheelAction, true);
  1371. TryDisableInputAction(m_TrackedDeviceOrientationAction, true);
  1372. TryDisableInputAction(m_TrackedDevicePositionAction, true);
  1373. }
  1374. private void EnableInputAction(InputActionReference inputActionReference)
  1375. {
  1376. var action = inputActionReference?.action;
  1377. if (action == null)
  1378. return;
  1379. if (s_InputActionReferenceCounts.TryGetValue(action, out var referenceState))
  1380. {
  1381. referenceState.refCount++;
  1382. s_InputActionReferenceCounts[action] = referenceState;
  1383. }
  1384. else
  1385. {
  1386. // if the action is already enabled but its reference count is zero then it was enabled by
  1387. // something outside the input module and the input module should never disable it.
  1388. referenceState = new InputActionReferenceState {refCount = 1, enabledByInputModule = !action.enabled};
  1389. s_InputActionReferenceCounts.Add(action, referenceState);
  1390. }
  1391. action.Enable();
  1392. }
  1393. private void TryDisableInputAction(InputActionReference inputActionReference, bool isComponentDisabling = false)
  1394. {
  1395. var action = inputActionReference?.action;
  1396. if (action == null)
  1397. return;
  1398. // Don't decrement refCount when we were not responsible for incrementing it.
  1399. // I.e. when we were not enabled yet. When OnDisabled is called, isActiveAndEnabled will
  1400. // already have been set to false. In that case we pass isComponentDisabling to check if we
  1401. // came from OnDisabled and therefore need to allow disabling.
  1402. if (!isActiveAndEnabled && !isComponentDisabling)
  1403. return;
  1404. if (!s_InputActionReferenceCounts.TryGetValue(action, out var referenceState))
  1405. return;
  1406. if (referenceState.refCount - 1 == 0 && referenceState.enabledByInputModule)
  1407. {
  1408. action.Disable();
  1409. s_InputActionReferenceCounts.Remove(action);
  1410. return;
  1411. }
  1412. referenceState.refCount--;
  1413. s_InputActionReferenceCounts[action] = referenceState;
  1414. }
  1415. private int GetPointerStateIndexFor(int pointerOrTouchId)
  1416. {
  1417. if (pointerOrTouchId == m_CurrentPointerId)
  1418. return m_CurrentPointerIndex;
  1419. for (var i = 0; i < m_PointerIds.length; ++i)
  1420. if (m_PointerIds[i] == pointerOrTouchId)
  1421. return i;
  1422. // Search for Device or Touch Ids as a fallback
  1423. for (var i = 0; i < m_PointerStates.length; ++i)
  1424. {
  1425. var eventData = m_PointerStates[i].eventData;
  1426. if (eventData.touchId == pointerOrTouchId || (eventData.touchId != 0 && eventData.device.deviceId == pointerOrTouchId))
  1427. return i;
  1428. }
  1429. return -1;
  1430. }
  1431. private ref PointerModel GetPointerStateForIndex(int index)
  1432. {
  1433. if (index == 0)
  1434. return ref m_PointerStates.firstValue;
  1435. return ref m_PointerStates.additionalValues[index - 1];
  1436. }
  1437. private int GetDisplayIndexFor(InputControl control)
  1438. {
  1439. int displayIndex = 0;
  1440. if (control.device is Pointer pointerCast)
  1441. {
  1442. displayIndex = pointerCast.displayIndex.ReadValue();
  1443. Debug.Assert(displayIndex <= byte.MaxValue, "Display index was larger than expected");
  1444. }
  1445. return displayIndex;
  1446. }
  1447. private int GetPointerStateIndexFor(ref InputAction.CallbackContext context)
  1448. {
  1449. if (CheckForRemovedDevice(ref context))
  1450. return -1;
  1451. var phase = context.phase;
  1452. return GetPointerStateIndexFor(context.control, createIfNotExists: phase != InputActionPhase.Canceled);
  1453. }
  1454. // This is the key method for determining which pointer a particular input is associated with.
  1455. // The principal determinant is the device that is sending the input which, in general, is expected
  1456. // to be a Pointer (Mouse, Pen, Touchscreen) or TrackedDevice.
  1457. //
  1458. // Note, however, that the input is not guaranteed to even come from a pointer-like device. One can
  1459. // bind the space key to a left click, for example. As long as we have an active pointer that can
  1460. // deliver position input, we accept that setup and treat pressing the space key the same as pressing
  1461. // the left button input on the respective pointer.
  1462. //
  1463. // Quite a lot going on in this method but we're dealing with three different UI interaction paradigms
  1464. // here which we all support from a single input path and allow seamless switching between.
  1465. private int GetPointerStateIndexFor(InputControl control, bool createIfNotExists = true)
  1466. {
  1467. Debug.Assert(control != null, "Control must not be null");
  1468. ////REVIEW: Any way we can cut down on the hops all over memory that we're doing here?
  1469. var device = control.device;
  1470. ////TODO: We're repeatedly inspecting the control setup here. Do this once and only redo it if the control setup changes.
  1471. ////REVIEW: It seems wrong that we are picking up an input here that is *NOT* reflected in our actions. We just end
  1472. //// up reading a touchId control implicitly instead of allowing actions to deliver IDs to us. On the other hand,
  1473. //// making that setup explicit in actions may be quite awkward and not nearly as robust.
  1474. // Determine the pointer (and touch) ID. We default the pointer ID to the device
  1475. // ID of the InputDevice.
  1476. var controlParent = control.parent;
  1477. var touchControlIndex = m_PointerTouchControls.IndexOfReference(controlParent);
  1478. if (touchControlIndex != -1)
  1479. {
  1480. // For touches, we cache a reference to the control of a pointer so that we don't
  1481. // have to continuously do ReadValue() on the touch ID control.
  1482. m_CurrentPointerId = m_PointerIds[touchControlIndex];
  1483. m_CurrentPointerIndex = touchControlIndex;
  1484. m_CurrentPointerType = UIPointerType.Touch;
  1485. return touchControlIndex;
  1486. }
  1487. var pointerId = device.deviceId;
  1488. var touchId = 0;
  1489. var touchPosition = Vector2.zero;
  1490. // Need to check if it's a touch so that we get a correct pointerId.
  1491. if (controlParent is TouchControl touchControl)
  1492. {
  1493. touchId = touchControl.touchId.value;
  1494. touchPosition = touchControl.position.value;
  1495. }
  1496. // Could be it's a toplevel control on Touchscreen (like "<Touchscreen>/position"). In that case,
  1497. // read the touch ID from primaryTouch.
  1498. else if (controlParent is Touchscreen touchscreen)
  1499. {
  1500. touchId = touchscreen.primaryTouch.touchId.value;
  1501. touchPosition = touchscreen.primaryTouch.position.value;
  1502. }
  1503. int displayIndex = GetDisplayIndexFor(control);
  1504. if (touchId != 0)
  1505. pointerId = ExtendedPointerEventData.MakePointerIdForTouch(pointerId, touchId);
  1506. // Early out if it's the last used pointer.
  1507. // NOTE: Can't just compare by device here because of touchscreens potentially having multiple associated pointers.
  1508. if (m_CurrentPointerId == pointerId)
  1509. return m_CurrentPointerIndex;
  1510. // Search m_PointerIds for an existing entry.
  1511. // NOTE: This is a linear search but m_PointerIds is only IDs and the number of concurrent pointers
  1512. // should be very low at any one point (in fact, we don't generally expect to have more than one
  1513. // which is why we are using InlinedArrays).
  1514. if (touchId == 0) // Not necessary for touches; see above.
  1515. {
  1516. for (var i = 0; i < m_PointerIds.length; i++)
  1517. {
  1518. if (m_PointerIds[i] == pointerId)
  1519. {
  1520. // Existing entry found. Make it the current pointer.
  1521. m_CurrentPointerId = pointerId;
  1522. m_CurrentPointerIndex = i;
  1523. m_CurrentPointerType = m_PointerStates[i].pointerType;
  1524. return i;
  1525. }
  1526. }
  1527. }
  1528. if (!createIfNotExists)
  1529. return -1;
  1530. // Determine pointer type.
  1531. var pointerType = UIPointerType.None;
  1532. if (touchId != 0)
  1533. pointerType = UIPointerType.Touch;
  1534. else if (HaveControlForDevice(device, point))
  1535. pointerType = UIPointerType.MouseOrPen;
  1536. else if (HaveControlForDevice(device, trackedDevicePosition))
  1537. pointerType = UIPointerType.Tracked;
  1538. ////REVIEW: For touch, probably makes sense to force-ignore any input other than from primaryTouch.
  1539. // If the behavior is SingleUnifiedPointer, we only ever create a single pointer state
  1540. // and use that for all pointer input that is coming in.
  1541. if ((m_PointerBehavior == UIPointerBehavior.SingleUnifiedPointer && pointerType != UIPointerType.None) ||
  1542. (m_PointerBehavior == UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack && pointerType == UIPointerType.MouseOrPen))
  1543. {
  1544. if (m_CurrentPointerIndex == -1)
  1545. {
  1546. m_CurrentPointerIndex = AllocatePointer(pointerId, displayIndex, touchId, pointerType, control, device, touchId != 0 ? controlParent : null);
  1547. }
  1548. else
  1549. {
  1550. // Update pointer record to reflect current device. We know they're different because we checked
  1551. // m_CurrentPointerId earlier in the method.
  1552. // NOTE: This path may repeatedly switch the pointer type and ID on the same single event instance.
  1553. ref var pointer = ref GetPointerStateForIndex(m_CurrentPointerIndex);
  1554. var eventData = pointer.eventData;
  1555. eventData.control = control;
  1556. eventData.device = device;
  1557. eventData.pointerType = pointerType;
  1558. eventData.pointerId = pointerId;
  1559. eventData.touchId = touchId;
  1560. #if UNITY_2022_3_OR_NEWER
  1561. eventData.displayIndex = displayIndex;
  1562. #endif
  1563. // Make sure these don't linger around when we switch to a different kind of pointer.
  1564. eventData.trackedDeviceOrientation = default;
  1565. eventData.trackedDevicePosition = default;
  1566. }
  1567. if (pointerType == UIPointerType.Touch)
  1568. GetPointerStateForIndex(m_CurrentPointerIndex).screenPosition = touchPosition;
  1569. m_CurrentPointerId = pointerId;
  1570. m_CurrentPointerType = pointerType;
  1571. return m_CurrentPointerIndex;
  1572. }
  1573. // No existing record for the device. Find out if the device has the ability to point at all.
  1574. // If not, we need to use a pointer state from a different device (if present).
  1575. var index = -1;
  1576. if (pointerType != UIPointerType.None)
  1577. {
  1578. // Device has an associated position input. Create a new pointer record.
  1579. index = AllocatePointer(pointerId, displayIndex, touchId, pointerType, control, device, touchId != 0 ? controlParent : null);
  1580. }
  1581. else
  1582. {
  1583. // Device has no associated position input. Find a pointer device to route the change into.
  1584. // As a last resort, create a pointer without a position input.
  1585. // If we have a current pointer, route the input into that. The majority of times we end
  1586. // up in this branch, this should settle things.
  1587. if (m_CurrentPointerId != -1)
  1588. return m_CurrentPointerIndex;
  1589. // NOTE: In most cases, we end up here when there is input on a non-pointer device bound to one of the pointer-related
  1590. // actions before there is input from a pointer device. In this scenario, we don't have a pointer state allocated
  1591. // for the device yet.
  1592. // If we have anything bound to the `point` action, create a pointer for it.
  1593. var pointControls = point?.action?.controls;
  1594. var pointerDevice = pointControls.HasValue && pointControls.Value.Count > 0 ? pointControls.Value[0].device : null;
  1595. if (pointerDevice != null && !(pointerDevice is Touchscreen)) // Touchscreen only temporarily allocate pointer states.
  1596. {
  1597. // Create MouseOrPen style pointer.
  1598. index = AllocatePointer(pointerDevice.deviceId, displayIndex, 0, UIPointerType.MouseOrPen, pointControls.Value[0], pointerDevice);
  1599. }
  1600. else
  1601. {
  1602. // Do the same but look at the `position` action.
  1603. var positionControls = trackedDevicePosition?.action?.controls;
  1604. var trackedDevice = positionControls.HasValue && positionControls.Value.Count > 0
  1605. ? positionControls.Value[0].device
  1606. : null;
  1607. if (trackedDevice != null)
  1608. {
  1609. // Create a Tracked style pointer.
  1610. index = AllocatePointer(trackedDevice.deviceId, displayIndex, 0, UIPointerType.Tracked, positionControls.Value[0], trackedDevice);
  1611. }
  1612. else
  1613. {
  1614. // We got input from a non-pointer device and apparently there's no pointer we can route the
  1615. // input into. Just create a pointer state for the device and leave it at that.
  1616. index = AllocatePointer(pointerId, displayIndex, 0, UIPointerType.None, control, device);
  1617. }
  1618. }
  1619. }
  1620. if (pointerType == UIPointerType.Touch)
  1621. GetPointerStateForIndex(index).screenPosition = touchPosition;
  1622. m_CurrentPointerId = pointerId;
  1623. m_CurrentPointerIndex = index;
  1624. m_CurrentPointerType = pointerType;
  1625. return index;
  1626. }
  1627. private int AllocatePointer(int pointerId, int displayIndex, int touchId, UIPointerType pointerType, InputControl control, InputDevice device, InputControl touchControl = null)
  1628. {
  1629. // Recover event instance from previous record.
  1630. var eventData = default(ExtendedPointerEventData);
  1631. if (m_PointerStates.Capacity > m_PointerStates.length)
  1632. {
  1633. if (m_PointerStates.length == 0)
  1634. eventData = m_PointerStates.firstValue.eventData;
  1635. else
  1636. eventData = m_PointerStates.additionalValues[m_PointerStates.length - 1].eventData;
  1637. }
  1638. // Or allocate event.
  1639. if (eventData == null)
  1640. eventData = new ExtendedPointerEventData(eventSystem);
  1641. eventData.pointerId = pointerId;
  1642. #if UNITY_2022_3_OR_NEWER
  1643. eventData.displayIndex = displayIndex;
  1644. #endif
  1645. eventData.touchId = touchId;
  1646. eventData.pointerType = pointerType;
  1647. eventData.control = control;
  1648. eventData.device = device;
  1649. // Allocate state.
  1650. m_PointerIds.AppendWithCapacity(pointerId);
  1651. m_PointerTouchControls.AppendWithCapacity(touchControl);
  1652. return m_PointerStates.AppendWithCapacity(new PointerModel(eventData));
  1653. }
  1654. private void SendPointerExitEventsAndRemovePointer(int index)
  1655. {
  1656. var eventData = m_PointerStates[index].eventData;
  1657. if (eventData.pointerEnter != null)
  1658. ProcessPointerMovement(eventData, null);
  1659. RemovePointerAtIndex(index);
  1660. }
  1661. private void RemovePointerAtIndex(int index)
  1662. {
  1663. Debug.Assert(m_PointerStates[index].eventData.pointerEnter == null, "Pointer should have exited all objects before being removed");
  1664. // Retain event data so that we can reuse the event the next time we allocate a PointerModel record.
  1665. var eventData = m_PointerStates[index].eventData;
  1666. Debug.Assert(eventData != null, "Pointer state should have an event instance!");
  1667. // Update current pointer, if necessary.
  1668. if (index == m_CurrentPointerIndex)
  1669. {
  1670. m_CurrentPointerId = -1;
  1671. m_CurrentPointerIndex = -1;
  1672. m_CurrentPointerType = default;
  1673. }
  1674. else if (m_CurrentPointerIndex == m_PointerIds.length - 1)
  1675. {
  1676. // We're about to move the last entry so update the index it will
  1677. // be at.
  1678. m_CurrentPointerIndex = index;
  1679. }
  1680. // Remove. Note that we may change the order of pointers here. This can save us needless copying
  1681. // and m_CurrentPointerIndex should be the only index we get around for longer.
  1682. m_PointerIds.RemoveAtByMovingTailWithCapacity(index);
  1683. m_PointerTouchControls.RemoveAtByMovingTailWithCapacity(index);
  1684. m_PointerStates.RemoveAtByMovingTailWithCapacity(index);
  1685. Debug.Assert(m_PointerIds.length == m_PointerStates.length, "Pointer ID array should match state array in length");
  1686. // Put event instance back in place at one past last entry of array (which we know we have
  1687. // as we just erased one entry). This entry will be the next one that will be used when we
  1688. // allocate a new entry.
  1689. // Wipe the event.
  1690. // NOTE: We only wipe properties here that contain reference data. The rest we rely on
  1691. // the event handling code to initialize when using the event.
  1692. eventData.hovered.Clear();
  1693. eventData.device = null;
  1694. eventData.pointerCurrentRaycast = default;
  1695. eventData.pointerPressRaycast = default;
  1696. eventData.pointerPress = default; // Twice to wipe lastPress, too.
  1697. eventData.pointerPress = default;
  1698. eventData.pointerDrag = default;
  1699. eventData.pointerEnter = default;
  1700. eventData.rawPointerPress = default;
  1701. if (m_PointerStates.length == 0)
  1702. m_PointerStates.firstValue.eventData = eventData;
  1703. else
  1704. m_PointerStates.additionalValues[m_PointerStates.length - 1].eventData = eventData;
  1705. }
  1706. // Remove any pointer that no longer has the ability to point.
  1707. private void PurgeStalePointers()
  1708. {
  1709. for (var i = 0; i < m_PointerStates.length; ++i)
  1710. {
  1711. ref var state = ref GetPointerStateForIndex(i);
  1712. var device = state.eventData.device;
  1713. if (!device.added || // Check if device was removed altogether.
  1714. (!HaveControlForDevice(device, point) &&
  1715. !HaveControlForDevice(device, trackedDevicePosition) &&
  1716. !HaveControlForDevice(device, trackedDeviceOrientation)))
  1717. {
  1718. SendPointerExitEventsAndRemovePointer(i);
  1719. --i;
  1720. }
  1721. }
  1722. m_NeedToPurgeStalePointers = false;
  1723. }
  1724. private static bool HaveControlForDevice(InputDevice device, InputActionReference actionReference)
  1725. {
  1726. var action = actionReference?.action;
  1727. if (action == null)
  1728. return false;
  1729. var controls = action.controls;
  1730. for (var i = 0; i < controls.Count; ++i)
  1731. if (controls[i].device == device)
  1732. return true;
  1733. return false;
  1734. }
  1735. // The pointer actions we unfortunately cannot poll as we may be sourcing input from multiple pointers.
  1736. private void OnPointCallback(InputAction.CallbackContext context)
  1737. {
  1738. // When a pointer is removed, there's like a non-zero coordinate on the position control and thus
  1739. // we will see cancellations on the "Point" action. Ignore these as they provide no useful values
  1740. // and we want to avoid doing a read of touch IDs in GetPointerStateFor() on an already removed
  1741. // touchscreen.
  1742. if (CheckForRemovedDevice(ref context) || context.canceled)
  1743. return;
  1744. var index = GetPointerStateIndexFor(context.control);
  1745. if (index == -1)
  1746. return;
  1747. ref var state = ref GetPointerStateForIndex(index);
  1748. state.screenPosition = context.ReadValue<Vector2>();
  1749. #if UNITY_2022_3_OR_NEWER
  1750. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1751. #endif
  1752. }
  1753. // NOTE: In the click events, we specifically react to the Canceled phase to make sure we do NOT perform
  1754. // button *clicks* when an action resets. However, we still need to send pointer ups.
  1755. private bool IgnoreNextClick(ref InputAction.CallbackContext context, bool wasPressed)
  1756. {
  1757. // If explicitly ignoring focus due to setting, never ignore clicks
  1758. if (explictlyIgnoreFocus)
  1759. return false;
  1760. // If a currently active click is cancelled (by focus change), ignore next click if device cannot run in background.
  1761. // This prevents the cancelled click event being registered when focus is returned i.e. if
  1762. // the button was released while another window was focused.
  1763. return context.canceled && !InputRuntime.s_Instance.isPlayerFocused && !context.control.device.canRunInBackground && wasPressed;
  1764. }
  1765. private void OnLeftClickCallback(InputAction.CallbackContext context)
  1766. {
  1767. var index = GetPointerStateIndexFor(ref context);
  1768. if (index == -1)
  1769. return;
  1770. ref var state = ref GetPointerStateForIndex(index);
  1771. bool wasPressed = state.leftButton.isPressed;
  1772. state.leftButton.isPressed = context.ReadValueAsButton();
  1773. state.changedThisFrame = true;
  1774. if (IgnoreNextClick(ref context, wasPressed))
  1775. state.leftButton.ignoreNextClick = true;
  1776. #if UNITY_2022_3_OR_NEWER
  1777. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1778. #endif
  1779. }
  1780. private void OnRightClickCallback(InputAction.CallbackContext context)
  1781. {
  1782. var index = GetPointerStateIndexFor(ref context);
  1783. if (index == -1)
  1784. return;
  1785. ref var state = ref GetPointerStateForIndex(index);
  1786. bool wasPressed = state.rightButton.isPressed;
  1787. state.rightButton.isPressed = context.ReadValueAsButton();
  1788. state.changedThisFrame = true;
  1789. if (IgnoreNextClick(ref context, wasPressed))
  1790. state.rightButton.ignoreNextClick = true;
  1791. #if UNITY_2022_3_OR_NEWER
  1792. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1793. #endif
  1794. }
  1795. private void OnMiddleClickCallback(InputAction.CallbackContext context)
  1796. {
  1797. var index = GetPointerStateIndexFor(ref context);
  1798. if (index == -1)
  1799. return;
  1800. ref var state = ref GetPointerStateForIndex(index);
  1801. bool wasPressed = state.middleButton.isPressed;
  1802. state.middleButton.isPressed = context.ReadValueAsButton();
  1803. state.changedThisFrame = true;
  1804. if (IgnoreNextClick(ref context, wasPressed))
  1805. state.middleButton.ignoreNextClick = true;
  1806. #if UNITY_2022_3_OR_NEWER
  1807. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1808. #endif
  1809. }
  1810. private bool CheckForRemovedDevice(ref InputAction.CallbackContext context)
  1811. {
  1812. // When a device is removed, we want to simply cancel ongoing pointer
  1813. // operations. Most importantly, we want to prevent GetPointerStateFor()
  1814. // doing ReadValue() on touch ID controls when a touchscreen has already
  1815. // been removed.
  1816. if (context.canceled && !context.control.device.added)
  1817. {
  1818. m_NeedToPurgeStalePointers = true;
  1819. return true;
  1820. }
  1821. return false;
  1822. }
  1823. internal const float kPixelPerLine = 20;
  1824. private void OnScrollCallback(InputAction.CallbackContext context)
  1825. {
  1826. var index = GetPointerStateIndexFor(ref context);
  1827. if (index == -1)
  1828. return;
  1829. ref var state = ref GetPointerStateForIndex(index);
  1830. // The old input system reported scroll deltas in lines, we report pixels.
  1831. // Need to scale as the UI system expects lines.
  1832. state.scrollDelta = context.ReadValue<Vector2>() * (1 / kPixelPerLine);
  1833. #if UNITY_2022_3_OR_NEWER
  1834. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1835. #endif
  1836. }
  1837. private void OnMoveCallback(InputAction.CallbackContext context)
  1838. {
  1839. ////REVIEW: should we poll this? or set the action to not be pass-through? (ps4 controller is spamming this action)
  1840. m_NavigationState.move = context.ReadValue<Vector2>();
  1841. }
  1842. private void OnTrackedDeviceOrientationCallback(InputAction.CallbackContext context)
  1843. {
  1844. var index = GetPointerStateIndexFor(ref context);
  1845. if (index == -1)
  1846. return;
  1847. ref var state = ref GetPointerStateForIndex(index);
  1848. state.worldOrientation = context.ReadValue<Quaternion>();
  1849. #if UNITY_2022_3_OR_NEWER
  1850. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1851. #endif
  1852. }
  1853. private void OnTrackedDevicePositionCallback(InputAction.CallbackContext context)
  1854. {
  1855. var index = GetPointerStateIndexFor(ref context);
  1856. if (index == -1)
  1857. return;
  1858. ref var state = ref GetPointerStateForIndex(index);
  1859. state.worldPosition = context.ReadValue<Vector3>();
  1860. #if UNITY_2022_3_OR_NEWER
  1861. state.eventData.displayIndex = GetDisplayIndexFor(context.control);
  1862. #endif
  1863. }
  1864. private void OnControlsChanged(object obj)
  1865. {
  1866. m_NeedToPurgeStalePointers = true;
  1867. }
  1868. private void FilterPointerStatesByType()
  1869. {
  1870. var pointerTypeToProcess = UIPointerType.None;
  1871. // Read all pointers device states
  1872. // Find first pointer that has changed this frame to be processed later
  1873. for (var i = 0; i < m_PointerStates.length; ++i)
  1874. {
  1875. ref var state = ref GetPointerStateForIndex(i);
  1876. state.eventData.ReadDeviceState();
  1877. state.CopyTouchOrPenStateFrom(state.eventData);
  1878. if (state.changedThisFrame && pointerTypeToProcess == UIPointerType.None)
  1879. pointerTypeToProcess = state.pointerType;
  1880. }
  1881. // For SingleMouseOrPenButMultiTouchAndTrack, we keep a single pointer for mouse and pen but only for as
  1882. // long as there is no touch or tracked input. If we get that kind, we remove the mouse/pen pointer.
  1883. if (m_PointerBehavior == UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack && pointerTypeToProcess != UIPointerType.None)
  1884. {
  1885. // var pointerTypeToProcess = m_PointerStates.firstValue.pointerType;
  1886. if (pointerTypeToProcess == UIPointerType.MouseOrPen)
  1887. {
  1888. // We have input on a mouse or pen. Kill all touch and tracked pointers we may have.
  1889. for (var i = 0; i < m_PointerStates.length; ++i)
  1890. {
  1891. if (m_PointerStates[i].pointerType != UIPointerType.MouseOrPen)
  1892. {
  1893. SendPointerExitEventsAndRemovePointer(i);
  1894. --i;
  1895. }
  1896. }
  1897. }
  1898. else
  1899. {
  1900. // We have touch or tracked input. Kill mouse/pen pointer, if we have it.
  1901. for (var i = 0; i < m_PointerStates.length; ++i)
  1902. {
  1903. if (m_PointerStates[i].pointerType == UIPointerType.MouseOrPen)
  1904. {
  1905. SendPointerExitEventsAndRemovePointer(i);
  1906. --i;
  1907. }
  1908. }
  1909. }
  1910. }
  1911. }
  1912. public override void Process()
  1913. {
  1914. if (m_NeedToPurgeStalePointers)
  1915. PurgeStalePointers();
  1916. // Reset devices of changes since we don't want to spool up changes once we gain focus.
  1917. if (!eventSystem.isFocused && !shouldIgnoreFocus)
  1918. {
  1919. for (var i = 0; i < m_PointerStates.length; ++i)
  1920. m_PointerStates[i].OnFrameFinished();
  1921. }
  1922. else
  1923. {
  1924. // Navigation input.
  1925. ProcessNavigation(ref m_NavigationState);
  1926. FilterPointerStatesByType();
  1927. // Pointer input.
  1928. for (var i = 0; i < m_PointerStates.length; i++)
  1929. {
  1930. ref var state = ref GetPointerStateForIndex(i);
  1931. ProcessPointer(ref state);
  1932. // If it's a touch and the touch has ended, release the pointer state.
  1933. // NOTE: We defer this by one frame such that OnPointerUp happens in the frame of release
  1934. // and OnPointerExit happens one frame later. This is so that IsPointerOverGameObject()
  1935. // stays true for the touch in the frame of release (see UI_TouchPointersAreKeptForOneFrameAfterRelease).
  1936. if (state.pointerType == UIPointerType.Touch && !state.leftButton.isPressed && !state.leftButton.wasReleasedThisFrame)
  1937. {
  1938. RemovePointerAtIndex(i);
  1939. --i;
  1940. continue;
  1941. }
  1942. state.OnFrameFinished();
  1943. }
  1944. }
  1945. }
  1946. #if UNITY_2021_1_OR_NEWER
  1947. public override int ConvertUIToolkitPointerId(PointerEventData sourcePointerData)
  1948. {
  1949. // Case 1369081: when using SingleUnifiedPointer, the same (default) pointerId should be sent to UIToolkit
  1950. // regardless of pointer type or finger id.
  1951. if (m_PointerBehavior == UIPointerBehavior.SingleUnifiedPointer)
  1952. return UIElements.PointerId.mousePointerId;
  1953. return sourcePointerData is ExtendedPointerEventData ep
  1954. ? ep.uiToolkitPointerId
  1955. : base.ConvertUIToolkitPointerId(sourcePointerData);
  1956. }
  1957. #endif
  1958. private void HookActions()
  1959. {
  1960. if (m_ActionsHooked)
  1961. return;
  1962. if (m_OnPointDelegate == null)
  1963. m_OnPointDelegate = OnPointCallback;
  1964. if (m_OnLeftClickDelegate == null)
  1965. m_OnLeftClickDelegate = OnLeftClickCallback;
  1966. if (m_OnRightClickDelegate == null)
  1967. m_OnRightClickDelegate = OnRightClickCallback;
  1968. if (m_OnMiddleClickDelegate == null)
  1969. m_OnMiddleClickDelegate = OnMiddleClickCallback;
  1970. if (m_OnScrollWheelDelegate == null)
  1971. m_OnScrollWheelDelegate = OnScrollCallback;
  1972. if (m_OnMoveDelegate == null)
  1973. m_OnMoveDelegate = OnMoveCallback;
  1974. if (m_OnTrackedDeviceOrientationDelegate == null)
  1975. m_OnTrackedDeviceOrientationDelegate = OnTrackedDeviceOrientationCallback;
  1976. if (m_OnTrackedDevicePositionDelegate == null)
  1977. m_OnTrackedDevicePositionDelegate = OnTrackedDevicePositionCallback;
  1978. SetActionCallbacks(true);
  1979. }
  1980. private void UnhookActions()
  1981. {
  1982. if (!m_ActionsHooked)
  1983. return;
  1984. SetActionCallbacks(false);
  1985. }
  1986. private void SetActionCallbacks(bool install)
  1987. {
  1988. m_ActionsHooked = install;
  1989. SetActionCallback(m_PointAction, m_OnPointDelegate, install);
  1990. SetActionCallback(m_MoveAction, m_OnMoveDelegate, install);
  1991. SetActionCallback(m_LeftClickAction, m_OnLeftClickDelegate, install);
  1992. SetActionCallback(m_RightClickAction, m_OnRightClickDelegate, install);
  1993. SetActionCallback(m_MiddleClickAction, m_OnMiddleClickDelegate, install);
  1994. SetActionCallback(m_ScrollWheelAction, m_OnScrollWheelDelegate, install);
  1995. SetActionCallback(m_TrackedDeviceOrientationAction, m_OnTrackedDeviceOrientationDelegate, install);
  1996. SetActionCallback(m_TrackedDevicePositionAction, m_OnTrackedDevicePositionDelegate, install);
  1997. }
  1998. private static void SetActionCallback(InputActionReference actionReference, Action<InputAction.CallbackContext> callback, bool install)
  1999. {
  2000. if (!install && callback == null)
  2001. return;
  2002. if (actionReference == null)
  2003. return;
  2004. var action = actionReference.action;
  2005. if (action == null)
  2006. return;
  2007. if (install)
  2008. {
  2009. action.performed += callback;
  2010. action.canceled += callback;
  2011. }
  2012. else
  2013. {
  2014. action.performed -= callback;
  2015. action.canceled -= callback;
  2016. }
  2017. }
  2018. private InputActionReference UpdateReferenceForNewAsset(InputActionReference actionReference)
  2019. {
  2020. var oldAction = actionReference?.action;
  2021. if (oldAction == null)
  2022. return null;
  2023. var oldActionMap = oldAction.actionMap;
  2024. Debug.Assert(oldActionMap != null, "Not expected to end up with a singleton action here");
  2025. var newActionMap = m_ActionsAsset?.FindActionMap(oldActionMap.name);
  2026. if (newActionMap == null)
  2027. return null;
  2028. var newAction = newActionMap.FindAction(oldAction.name);
  2029. if (newAction == null)
  2030. return null;
  2031. return InputActionReference.Create(newAction);
  2032. }
  2033. public InputActionAsset actionsAsset
  2034. {
  2035. get => m_ActionsAsset;
  2036. set
  2037. {
  2038. if (value != m_ActionsAsset)
  2039. {
  2040. UnhookActions();
  2041. m_ActionsAsset = value;
  2042. point = UpdateReferenceForNewAsset(point);
  2043. move = UpdateReferenceForNewAsset(move);
  2044. leftClick = UpdateReferenceForNewAsset(leftClick);
  2045. rightClick = UpdateReferenceForNewAsset(rightClick);
  2046. middleClick = UpdateReferenceForNewAsset(middleClick);
  2047. scrollWheel = UpdateReferenceForNewAsset(scrollWheel);
  2048. submit = UpdateReferenceForNewAsset(submit);
  2049. cancel = UpdateReferenceForNewAsset(cancel);
  2050. trackedDeviceOrientation = UpdateReferenceForNewAsset(trackedDeviceOrientation);
  2051. trackedDevicePosition = UpdateReferenceForNewAsset(trackedDevicePosition);
  2052. HookActions();
  2053. }
  2054. }
  2055. }
  2056. [SerializeField, HideInInspector] private InputActionAsset m_ActionsAsset;
  2057. [SerializeField, HideInInspector] private InputActionReference m_PointAction;
  2058. [SerializeField, HideInInspector] private InputActionReference m_MoveAction;
  2059. [SerializeField, HideInInspector] private InputActionReference m_SubmitAction;
  2060. [SerializeField, HideInInspector] private InputActionReference m_CancelAction;
  2061. [SerializeField, HideInInspector] private InputActionReference m_LeftClickAction;
  2062. [SerializeField, HideInInspector] private InputActionReference m_MiddleClickAction;
  2063. [SerializeField, HideInInspector] private InputActionReference m_RightClickAction;
  2064. [SerializeField, HideInInspector] private InputActionReference m_ScrollWheelAction;
  2065. [SerializeField, HideInInspector] private InputActionReference m_TrackedDevicePositionAction;
  2066. [SerializeField, HideInInspector] private InputActionReference m_TrackedDeviceOrientationAction;
  2067. [SerializeField] private bool m_DeselectOnBackgroundClick = true;
  2068. [SerializeField] private UIPointerBehavior m_PointerBehavior = UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack;
  2069. [SerializeField, HideInInspector] internal CursorLockBehavior m_CursorLockBehavior = CursorLockBehavior.OutsideScreen;
  2070. private static Dictionary<InputAction, InputActionReferenceState> s_InputActionReferenceCounts = new Dictionary<InputAction, InputActionReferenceState>();
  2071. private struct InputActionReferenceState
  2072. {
  2073. public int refCount;
  2074. public bool enabledByInputModule;
  2075. }
  2076. [NonSerialized] private bool m_ActionsHooked;
  2077. [NonSerialized] private bool m_NeedToPurgeStalePointers;
  2078. private Action<InputAction.CallbackContext> m_OnPointDelegate;
  2079. private Action<InputAction.CallbackContext> m_OnMoveDelegate;
  2080. private Action<InputAction.CallbackContext> m_OnLeftClickDelegate;
  2081. private Action<InputAction.CallbackContext> m_OnRightClickDelegate;
  2082. private Action<InputAction.CallbackContext> m_OnMiddleClickDelegate;
  2083. private Action<InputAction.CallbackContext> m_OnScrollWheelDelegate;
  2084. private Action<InputAction.CallbackContext> m_OnTrackedDevicePositionDelegate;
  2085. private Action<InputAction.CallbackContext> m_OnTrackedDeviceOrientationDelegate;
  2086. private Action<object> m_OnControlsChangedDelegate;
  2087. // Pointer-type input (also tracking-type).
  2088. [NonSerialized] private int m_CurrentPointerId = -1; // Keeping track of the current pointer avoids searches in most cases.
  2089. [NonSerialized] private int m_CurrentPointerIndex = -1;
  2090. [NonSerialized] internal UIPointerType m_CurrentPointerType = UIPointerType.None;
  2091. internal InlinedArray<int> m_PointerIds; // Index in this array maps to index in m_PointerStates. Separated out to make searching more efficient (we do a linear search).
  2092. internal InlinedArray<InputControl> m_PointerTouchControls;
  2093. internal InlinedArray<PointerModel> m_PointerStates;
  2094. // Navigation-type input.
  2095. private NavigationModel m_NavigationState;
  2096. [NonSerialized] private GameObject m_LocalMultiPlayerRoot;
  2097. /// <summary>
  2098. /// Controls the origin point of raycasts when the cursor is locked.
  2099. /// </summary>
  2100. public enum CursorLockBehavior
  2101. {
  2102. /// <summary>
  2103. /// The internal pointer position will be set to -1, -1. This short-circuits the raycasting
  2104. /// logic so no objects will be intersected. This is the default setting.
  2105. /// </summary>
  2106. OutsideScreen,
  2107. /// <summary>
  2108. /// Raycasts will originate from the center of the screen. This mode can be useful for
  2109. /// example to check in pointer-driven FPS games if the player is looking at some world-space
  2110. /// object that implements the <see cref="IPointerEnterHandler"/> and <see cref="IPointerExitHandler"/>
  2111. /// interfaces.
  2112. /// </summary>
  2113. ScreenCenter
  2114. }
  2115. }
  2116. }
  2117. #endif