1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398 |
- #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
- using System;
- using System.Collections.Generic;
- using UnityEngine.EventSystems;
- using UnityEngine.InputSystem.Controls;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEngine.InputSystem.Utilities;
- using UnityEngine.Serialization;
- using UnityEngine.UI;
- #if UNITY_EDITOR
- using UnityEditor;
- #endif
-
- ////FIXME: The UI is currently not reacting to pointers until they are moved after the UI module has been enabled. What needs to
- //// happen is that point, trackedDevicePosition, and trackedDeviceOrientation have initial state checks. However, for touch,
- //// we do *not* want to react to the initial value as then we also get presses (unlike with other pointers). Argh.
-
- ////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)
- //// I.e. there'd be a PointerInput, a NavigationInput, and a TrackedInput composite. This would solve several problems in one go and make
- //// it much more obvious which inputs go together.
- //// NOTE: This does not actually solve the problem. Even if, for example, we have a PointerInput value struct and a PointerInputComposite
- //// 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
- //// touchscreens and thus multiple touches coming in through the same composite. This leads back to the same situation.
-
- ////REVIEW: The current input model has too much complexity for pointer input; find a way to simplify this.
-
- ////REVIEW: how does this/uGUI support drag-scrolls on touch? [GESTURES]
-
- ////REVIEW: how does this/uGUI support two-finger right-clicks with touch? [GESTURES]
-
- ////TODO: add ability to query which device was last used with any of the actions
- ////REVIEW: also give access to the last/current UI event?
-
- ////TODO: ToString() method a la PointerInputModule
-
- namespace UnityEngine.InputSystem.UI
- {
- /// <summary>
- /// Input module that takes its input from <see cref="InputAction">input actions</see>.
- /// </summary>
- /// <remarks>
- /// This UI input module has the advantage over other such modules that it doesn't have to know
- /// what devices and types of devices input is coming from. Instead, the actions hide the actual
- /// sources of input from the module.
- ///
- /// When adding this component from code (such as through <c>GameObject.AddComponent</c>), the
- /// resulting module will automatically have a set of default input actions assigned to it
- /// (see <see cref="AssignDefaultActions"/>).
- /// </remarks>
- [HelpURL(InputSystem.kDocUrl + "/manual/UISupport.html#setting-up-ui-input")]
- public class InputSystemUIInputModule : BaseInputModule
- {
- /// <summary>
- /// Whether to clear the current selection when a click happens that does not hit any <c>GameObject</c>.
- /// </summary>
- /// <value>If true (default), clicking outside of any GameObject will reset the current selection.</value>
- /// <remarks>
- /// By toggling this behavior off, background clicks will keep the current selection. I.e.
- /// <c>EventSystem.currentSelectedGameObject</c> will not be changed.
- /// </remarks>
- public bool deselectOnBackgroundClick
- {
- get => m_DeselectOnBackgroundClick;
- set => m_DeselectOnBackgroundClick = value;
- }
-
- /// <summary>
- /// How to deal with the presence of pointer-type input from multiple devices.
- /// </summary>
- /// <remarks>
- /// By default, this is set to <see cref="UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack"/> which will
- /// treat input from <see cref="Mouse"/> and <see cref="Pen"/> devices as coming from a single on-screen pointer
- /// but will treat input from devices such as <see cref="XR.XRController"/> and <see cref="Touchscreen"/> as
- /// their own discrete pointers.
- ///
- /// The primary effect of this setting is to determine whether the user can concurrently point at more than
- /// a single UI element or not. Whenever multiple pointers are allowed, more than one element may have a pointer
- /// over it at any one point and thus several elements can be interacted with concurrently.
- /// </remarks>
- public UIPointerBehavior pointerBehavior
- {
- get => m_PointerBehavior;
- set => m_PointerBehavior = value;
- }
-
- /// <summary>
- /// Where to position the pointer when the cursor is locked.
- /// </summary>
- /// <remarks>
- /// By default, the pointer is positioned at -1, -1 in screen space when the cursor is locked. This has implications
- /// for using ray casters like <see cref="PhysicsRaycaster"/> because the raycasts will be sent from the pointer
- /// position. By setting the value of <see cref="cursorLockBehavior"/> to <see cref="CursorLockBehavior.ScreenCenter"/>,
- /// the raycasts will be sent from the center of the screen. This is useful when trying to interact with world space UI
- /// using the <see cref="IPointerEnterHandler"/> and <see cref="IPointerExitHandler"/> interfaces when the cursor
- /// is locked.
- /// </remarks>
- /// <see cref="Cursor.lockState"/>
- public CursorLockBehavior cursorLockBehavior
- {
- get => m_CursorLockBehavior;
- set => m_CursorLockBehavior = value;
- }
-
- /// <summary>
- /// A root game object to support correct navigation in local multi-player UIs.
- /// <remarks>
- /// In local multi-player games where each player has their own UI, players should not be able to navigate into
- /// another player's UI. Each player should have their own instance of an InputSystemUIInputModule, and this property
- /// should be set to the root game object containing all UI objects for that player. If set, navigation using the
- /// <see cref="InputSystemUIInputModule.move"/> action will be constrained to UI objects under that root.
- /// </remarks>
- /// </summary>
- internal GameObject localMultiPlayerRoot
- {
- get => m_LocalMultiPlayerRoot;
- set => m_LocalMultiPlayerRoot = value;
- }
-
- /// <summary>
- /// Called by <c>EventSystem</c> when the input module is made current.
- /// </summary>
- public override void ActivateModule()
- {
- base.ActivateModule();
-
- // Select firstSelectedGameObject if nothing is selected ATM.
- var toSelect = eventSystem.currentSelectedGameObject;
- if (toSelect == null)
- toSelect = eventSystem.firstSelectedGameObject;
- eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
- }
-
- /// <summary>
- /// Check whether the given pointer or touch is currently hovering over a <c>GameObject</c>.
- /// </summary>
- /// <param name="pointerOrTouchId">ID of the pointer or touch. Meaning this should correspond to either
- /// <c>PointerEventData.pointerId</c> or <see cref="ExtendedPointerEventData.touchId"/>. The pointer ID
- /// generally corresponds to the <see cref="InputDevice.deviceId"/> of the pointer device. An exception
- /// to this are touches as a <see cref="Touchscreen"/> may have multiple pointers (one for each active
- /// finger). For touch, you can use the <see cref="TouchControl.touchId"/> of the touch.
- ///
- /// Note that for touch, a pointer will stay valid for one frame before being removed. In other words,
- /// when <see cref="TouchPhase.Ended"/> or <see cref="TouchPhase.Canceled"/> is received for a touch
- /// and the touch was over a <c>GameObject</c>, the associated pointer is still considered over that
- /// object for the frame in which the touch ended.
- ///
- /// To check whether any pointer is over a <c>GameObject</c>, simply pass a negative value such as -1.</param>
- /// <returns>True if the given pointer is currently hovering over a <c>GameObject</c>.</returns>
- /// <remarks>
- /// The result is true if the given pointer has caused an <c>IPointerEnter</c> event to be sent to a
- /// <c>GameObject</c>.
- ///
- /// This method can be invoked via <c>EventSystem.current.IsPointerOverGameObject</c>.
- ///
- /// Be aware that this method relies on state set up during UI event processing that happens in <c>EventSystem.Update</c>,
- /// that is, as part of <c>MonoBehaviour</c> updates. This step happens <em>after</em> input processing.
- /// Thus, calling this method earlier than that in the frame will make it poll state from <em>last</em> frame.
- ///
- /// Calling this method from within an <see cref="InputAction"/> callback (such as <see cref="InputAction.performed"/>)
- /// will result in a warning. See the "UI vs Game Input" sample shipped with the Input System package for
- /// how to deal with this fact.
- ///
- /// <example>
- /// <code>
- /// // In general, the pointer ID corresponds to the device ID:
- /// EventSystem.current.IsPointerOverGameObject(XRController.leftHand.deviceId);
- /// EventSystem.current.IsPointerOverGameObject(Mouse.current.deviceId);
- ///
- /// // For touch input, pass the ID of a touch:
- /// EventSystem.current.IsPointerOverGameObject(Touchscreen.primaryTouch.touchId.ReadValue());
- ///
- /// // But can also pass the ID of the entire Touchscreen in which case the result
- /// // is true if any touch is over a GameObject:
- /// EventSystem.current.IsPointerOverGameObject(Touchscreen.current.deviceId);
- ///
- /// // Finally, any negative value will be interpreted as "any pointer" and will
- /// // return true if any one pointer is currently over a GameObject:
- /// EventSystem.current.IsPointerOverGameObject(-1);
- /// EventSystem.current.IsPointerOverGameObject(); // Equivalent.
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="ExtendedPointerEventData.touchId"/>
- /// <seealso cref="InputDevice.deviceId"/>
- public override bool IsPointerOverGameObject(int pointerOrTouchId)
- {
- if (InputSystem.isProcessingEvents)
- Debug.LogWarning(
- "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");
-
- var stateIndex = -1;
-
- if (pointerOrTouchId < 0)
- {
- if (m_CurrentPointerId != -1)
- {
- stateIndex = m_CurrentPointerIndex;
- }
- else
- {
- // No current pointer. Can happen, for example, when a touch just ended and its pointer record
- // was removed as a result. If we still have some active pointer, use it.
- if (m_PointerStates.length > 0)
- stateIndex = 0;
- }
- }
- else
- {
- stateIndex = GetPointerStateIndexFor(pointerOrTouchId);
- }
-
- if (stateIndex == -1)
- return false;
-
- return m_PointerStates[stateIndex].eventData.pointerEnter != null;
- }
-
- /// <summary>
- /// Returns the most recent raycast information for a given pointer or touch.
- /// </summary>
- /// <param name="pointerOrTouchId">ID of the pointer or touch. Meaning this should correspond to either
- /// <c>PointerEventData.pointerId</c> or <see cref="ExtendedPointerEventData.touchId"/>. The pointer ID
- /// generally corresponds to the <see cref="InputDevice.deviceId"/> of the pointer device. An exception
- /// to this are touches as a <see cref="Touchscreen"/> may have multiple pointers (one for each active
- /// finger). For touch, you can use the <see cref="TouchControl.touchId"/> of the touch.
- ///
- /// Negative values will return an invalid <see cref="RaycastResult"/>.</param>
- /// <returns>The most recent raycast information.</returns>
- /// <remarks>
- /// This method is for the most recent raycast, but depending on when it's called is not guaranteed to be for the current frame.
- /// This method can be used to determine raycast distances and hit information for visualization.
- /// <br />
- /// Use <see cref="RaycastResult.isValid"/> to determine if pointer hit anything.
- /// </remarks>
- /// <seealso cref="ExtendedPointerEventData.touchId"/>
- /// <seealso cref="InputDevice.deviceId"/>
- public RaycastResult GetLastRaycastResult(int pointerOrTouchId)
- {
- var stateIndex = GetPointerStateIndexFor(pointerOrTouchId);
- if (stateIndex == -1)
- return default;
-
- return m_PointerStates[stateIndex].eventData.pointerCurrentRaycast;
- }
-
- private RaycastResult PerformRaycast(ExtendedPointerEventData eventData)
- {
- if (eventData == null)
- throw new ArgumentNullException(nameof(eventData));
-
- // If it's an event from a tracked device, see if we have a TrackedDeviceRaycaster and give it
- // the first shot.
- if (eventData.pointerType == UIPointerType.Tracked && TrackedDeviceRaycaster.s_Instances.length > 0)
- {
- for (var i = 0; i < TrackedDeviceRaycaster.s_Instances.length; ++i)
- {
- var trackedDeviceRaycaster = TrackedDeviceRaycaster.s_Instances[i];
- m_RaycastResultCache.Clear();
- trackedDeviceRaycaster.PerformRaycast(eventData, m_RaycastResultCache);
- if (m_RaycastResultCache.Count > 0)
- {
- var raycastResult = m_RaycastResultCache[0];
- m_RaycastResultCache.Clear();
- return raycastResult;
- }
- }
- return default;
- }
-
- // Otherwise pass it along to the normal raycasting logic.
- eventSystem.RaycastAll(eventData, m_RaycastResultCache);
- var result = FindFirstRaycast(m_RaycastResultCache);
- m_RaycastResultCache.Clear();
- return result;
- }
-
- // Mouse, pen, touch, and tracked device pointer input all go through here.
- private void ProcessPointer(ref PointerModel state)
- {
- var eventData = state.eventData;
-
- // Sync position.
- var pointerType = eventData.pointerType;
- if (pointerType == UIPointerType.MouseOrPen && Cursor.lockState == CursorLockMode.Locked)
- {
- eventData.position = m_CursorLockBehavior == CursorLockBehavior.OutsideScreen ?
- new Vector2(-1, -1) :
- new Vector2(Screen.width / 2f, Screen.height / 2f);
- ////REVIEW: This is consistent with StandaloneInputModule but having no deltas in locked mode seems wrong
- eventData.delta = default;
- }
- else if (pointerType == UIPointerType.Tracked)
- {
- var position = state.worldPosition;
- var rotation = state.worldOrientation;
- if (m_XRTrackingOrigin != null)
- {
- position = m_XRTrackingOrigin.TransformPoint(position);
- rotation = m_XRTrackingOrigin.rotation * rotation;
- }
-
- eventData.trackedDeviceOrientation = rotation;
- eventData.trackedDevicePosition = position;
- }
- else
- {
- eventData.delta = state.screenPosition - eventData.position;
- eventData.position = state.screenPosition;
- }
-
- // Clear the 'used' flag.
- eventData.Reset();
-
- // Raycast from current position.
- eventData.pointerCurrentRaycast = PerformRaycast(eventData);
-
- // Sync position for tracking devices. For those, we can only do this
- // after the raycast as the screen-space position is a byproduct of the raycast.
- if (pointerType == UIPointerType.Tracked && eventData.pointerCurrentRaycast.isValid)
- {
- var screenPos = eventData.pointerCurrentRaycast.screenPosition;
- eventData.delta = screenPos - eventData.position;
- eventData.position = eventData.pointerCurrentRaycast.screenPosition;
- }
-
- ////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
-
- // Left mouse button. Movement and scrolling is processed with event set left button.
- eventData.button = PointerEventData.InputButton.Left;
- state.leftButton.CopyPressStateTo(eventData);
-
- // Unlike StandaloneInputModule, we process moves before processing buttons. This way
- // UI elements get pointer enters/exits before they get button ups/downs and clicks.
- ProcessPointerMovement(ref state, eventData);
-
- // We always need to process move-related events in order to get PointerEnter and Exit events
- // when we change UI state (e.g. show/hide objects) without moving the pointer. This unfortunately
- // also means that we will invariably raycast on every update.
- // However, after that, early out at this point when there's no changes to the pointer state (except
- // for tracked pointers as the tracking origin may have moved).
- if (!state.changedThisFrame && (xrTrackingOrigin == null || state.pointerType != UIPointerType.Tracked))
- return;
-
- ProcessPointerButton(ref state.leftButton, eventData);
- ProcessPointerButtonDrag(ref state.leftButton, eventData);
- ProcessPointerScroll(ref state, eventData);
-
- // Right mouse button.
- eventData.button = PointerEventData.InputButton.Right;
- state.rightButton.CopyPressStateTo(eventData);
-
- ProcessPointerButton(ref state.rightButton, eventData);
- ProcessPointerButtonDrag(ref state.rightButton, eventData);
-
- // Middle mouse button.
- eventData.button = PointerEventData.InputButton.Middle;
- state.middleButton.CopyPressStateTo(eventData);
-
- ProcessPointerButton(ref state.middleButton, eventData);
- ProcessPointerButtonDrag(ref state.middleButton, eventData);
- }
-
- // if we are using a MultiplayerEventSystem, ignore any transforms
- // not under the current MultiplayerEventSystem's root.
- private bool PointerShouldIgnoreTransform(Transform t)
- {
- if (eventSystem is MultiplayerEventSystem multiplayerEventSystem && multiplayerEventSystem.playerRoot != null)
- {
- if (!t.IsChildOf(multiplayerEventSystem.playerRoot.transform))
- return true;
- }
- return false;
- }
-
- private void ProcessPointerMovement(ref PointerModel pointer, ExtendedPointerEventData eventData)
- {
- var currentPointerTarget =
- // If the pointer is a touch that was released the *previous* frame, we generate pointer-exit events
- // and then later remove the pointer.
- eventData.pointerType == UIPointerType.Touch && !pointer.leftButton.isPressed && !pointer.leftButton.wasReleasedThisFrame
- ? null
- : eventData.pointerCurrentRaycast.gameObject;
-
- ProcessPointerMovement(eventData, currentPointerTarget);
- }
-
- private void ProcessPointerMovement(ExtendedPointerEventData eventData, GameObject currentPointerTarget)
- {
- #if UNITY_2021_1_OR_NEWER
- // If the pointer moved, send move events to all UI elements the pointer is
- // currently over.
- var wasMoved = eventData.IsPointerMoving();
- if (wasMoved)
- {
- for (var i = 0; i < eventData.hovered.Count; ++i)
- ExecuteEvents.Execute(eventData.hovered[i], eventData, ExecuteEvents.pointerMoveHandler);
- }
- #endif
-
- // If we have no target or pointerEnter has been deleted,
- // we just send exit events to anything we are tracking
- // and then exit.
- if (currentPointerTarget == null || eventData.pointerEnter == null)
- {
- for (var i = 0; i < eventData.hovered.Count; ++i)
- ExecuteEvents.Execute(eventData.hovered[i], eventData, ExecuteEvents.pointerExitHandler);
-
- eventData.hovered.Clear();
-
- if (currentPointerTarget == null)
- {
- eventData.pointerEnter = null;
- return;
- }
- }
-
- if (eventData.pointerEnter == currentPointerTarget && currentPointerTarget)
- return;
-
- var commonRoot = FindCommonRoot(eventData.pointerEnter, currentPointerTarget)?.transform;
-
- // We walk up the tree until a common root and the last entered and current entered object is found.
- // Then send exit and enter events up to, but not including, the common root.
- if (eventData.pointerEnter != null)
- {
- for (var current = eventData.pointerEnter.transform; current != null && current != commonRoot; current = current.parent)
- {
- ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerExitHandler);
- eventData.hovered.Remove(current.gameObject);
- }
- }
-
- eventData.pointerEnter = currentPointerTarget;
- if (currentPointerTarget != null)
- {
- for (var current = currentPointerTarget.transform;
- current != null && current != commonRoot && !PointerShouldIgnoreTransform(current);
- current = current.parent)
- {
- ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerEnterHandler);
- #if UNITY_2021_1_OR_NEWER
- if (wasMoved)
- ExecuteEvents.Execute(current.gameObject, eventData, ExecuteEvents.pointerMoveHandler);
- #endif
- eventData.hovered.Add(current.gameObject);
- }
- }
- }
-
- private const float kClickSpeed = 0.3f;
-
- private void ProcessPointerButton(ref PointerModel.ButtonState button, PointerEventData eventData)
- {
- var currentOverGo = eventData.pointerCurrentRaycast.gameObject;
-
- if (currentOverGo != null && PointerShouldIgnoreTransform(currentOverGo.transform))
- return;
-
- // Button press.
- if (button.wasPressedThisFrame)
- {
- button.pressTime = InputRuntime.s_Instance.unscaledGameTime;
-
- eventData.delta = Vector2.zero;
- eventData.dragging = false;
- eventData.pressPosition = eventData.position;
- eventData.pointerPressRaycast = eventData.pointerCurrentRaycast;
- eventData.eligibleForClick = true;
- eventData.useDragThreshold = true;
-
- var selectHandler = ExecuteEvents.GetEventHandler<ISelectHandler>(currentOverGo);
-
- // If we have clicked something new, deselect the old thing and leave 'selection handling' up
- // to the press event (except if there's none and we're told to not deselect in that case).
- if (selectHandler != eventSystem.currentSelectedGameObject && (selectHandler != null || m_DeselectOnBackgroundClick))
- eventSystem.SetSelectedGameObject(null, eventData);
-
- // Invoke OnPointerDown, if present.
- var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, eventData, ExecuteEvents.pointerDownHandler);
-
- // If no GO responded to OnPointerDown, look for one that responds to OnPointerClick.
- // NOTE: This only looks up the handler. We don't invoke OnPointerClick here.
- if (newPressed == null)
- newPressed = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
-
- // Reset click state if delay to last release was too long or if we didn't
- // press on the same object as last time. The latter part we don't know until
- // we've actually run the press handler.
- button.clickedOnSameGameObject = newPressed == eventData.lastPress && button.pressTime - eventData.clickTime <= kClickSpeed;
- if (eventData.clickCount > 0 && !button.clickedOnSameGameObject)
- {
- eventData.clickCount = default;
- eventData.clickTime = default;
- }
-
- // Set pointerPress. This nukes lastPress. Meaning that after OnPointerDown, lastPress will
- // become null.
- eventData.pointerPress = newPressed;
- eventData.rawPointerPress = currentOverGo;
-
- // Save the drag handler for drag events during this mouse down.
- eventData.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
-
- if (eventData.pointerDrag != null)
- ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.initializePotentialDrag);
- }
-
- // Button release.
- if (button.wasReleasedThisFrame)
- {
- // Check for click. Release must be on same GO that we pressed on and we must not
- // have moved beyond our move tolerance (doing so will set eligibleForClick to false).
- // NOTE: There's two difference to click handling here compared to StandaloneInputModule.
- // 1) StandaloneInputModule counts clicks entirely on press meaning that clickCount is increased
- // before a click has actually happened.
- // 2) StandaloneInputModule increases click counts even if something is eventually not deemed a
- // click and OnPointerClick is thus never invoked.
- var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
- var isClick = eventData.pointerPress == pointerClickHandler && eventData.eligibleForClick;
- if (isClick)
- {
- // Count clicks.
- if (button.clickedOnSameGameObject)
- {
- // We re-clicked on the same UI element within 0.3 seconds so count
- // it as a repeat click.
- ++eventData.clickCount;
- }
- else
- {
- // First click on this object.
- eventData.clickCount = 1;
- }
- eventData.clickTime = InputRuntime.s_Instance.unscaledGameTime;
- }
-
- // Invoke OnPointerUp.
- ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerUpHandler);
-
- // Invoke OnPointerClick or OnDrop.
- if (isClick)
- ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerClickHandler);
- else if (eventData.dragging && eventData.pointerDrag != null)
- ExecuteEvents.ExecuteHierarchy(currentOverGo, eventData, ExecuteEvents.dropHandler);
-
- eventData.eligibleForClick = false;
- eventData.pointerPress = null;
- eventData.rawPointerPress = null;
-
- if (eventData.dragging && eventData.pointerDrag != null)
- ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.endDragHandler);
-
- eventData.dragging = false;
- eventData.pointerDrag = null;
-
- button.ignoreNextClick = false;
- }
-
- button.CopyPressStateFrom(eventData);
- }
-
- private void ProcessPointerButtonDrag(ref PointerModel.ButtonState button, ExtendedPointerEventData eventData)
- {
- if (!eventData.IsPointerMoving() ||
- (eventData.pointerType == UIPointerType.MouseOrPen && Cursor.lockState == CursorLockMode.Locked) ||
- eventData.pointerDrag == null)
- return;
-
- // Detect drags.
- if (!eventData.dragging)
- {
- if (!eventData.useDragThreshold || (eventData.pressPosition - eventData.position).sqrMagnitude >=
- (double)eventSystem.pixelDragThreshold * eventSystem.pixelDragThreshold * (eventData.pointerType == UIPointerType.Tracked
- ? m_TrackedDeviceDragThresholdMultiplier
- : 1))
- {
- // Started dragging. Invoke OnBeginDrag.
- ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.beginDragHandler);
- eventData.dragging = true;
- }
- }
-
- if (eventData.dragging)
- {
- // If we moved from our initial press object, process an up for that object.
- if (eventData.pointerPress != eventData.pointerDrag)
- {
- ExecuteEvents.Execute(eventData.pointerPress, eventData, ExecuteEvents.pointerUpHandler);
-
- eventData.eligibleForClick = false;
- eventData.pointerPress = null;
- eventData.rawPointerPress = null;
- }
-
- ExecuteEvents.Execute(eventData.pointerDrag, eventData, ExecuteEvents.dragHandler);
- button.CopyPressStateFrom(eventData);
- }
- }
-
- private static void ProcessPointerScroll(ref PointerModel pointer, PointerEventData eventData)
- {
- var scrollDelta = pointer.scrollDelta;
- if (!Mathf.Approximately(scrollDelta.sqrMagnitude, 0.0f))
- {
- eventData.scrollDelta = scrollDelta;
- var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(eventData.pointerEnter);
- ExecuteEvents.ExecuteHierarchy(scrollHandler, eventData, ExecuteEvents.scrollHandler);
- }
- }
-
- internal void ProcessNavigation(ref NavigationModel navigationState)
- {
- var usedSelectionChange = false;
- if (eventSystem.currentSelectedGameObject != null)
- {
- var data = GetBaseEventData();
- ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);
- usedSelectionChange = data.used;
- }
-
- // Don't send move events if disabled in the EventSystem.
- if (!eventSystem.sendNavigationEvents)
- return;
-
- // Process move.
- var movement = navigationState.move;
- if (!usedSelectionChange && (!Mathf.Approximately(movement.x, 0f) || !Mathf.Approximately(movement.y, 0f)))
- {
- var time = InputRuntime.s_Instance.unscaledGameTime;
- var moveVector = navigationState.move;
-
- var moveDirection = MoveDirection.None;
- if (moveVector.sqrMagnitude > 0)
- {
- if (Mathf.Abs(moveVector.x) > Mathf.Abs(moveVector.y))
- moveDirection = moveVector.x > 0 ? MoveDirection.Right : MoveDirection.Left;
- else
- moveDirection = moveVector.y > 0 ? MoveDirection.Up : MoveDirection.Down;
- }
-
- ////REVIEW: is resetting move repeats when direction changes really useful behavior?
- if (moveDirection != m_NavigationState.lastMoveDirection)
- m_NavigationState.consecutiveMoveCount = 0;
-
- if (moveDirection != MoveDirection.None)
- {
- var allow = true;
- if (m_NavigationState.consecutiveMoveCount != 0)
- {
- if (m_NavigationState.consecutiveMoveCount > 1)
- allow = time > m_NavigationState.lastMoveTime + moveRepeatRate;
- else
- allow = time > m_NavigationState.lastMoveTime + moveRepeatDelay;
- }
-
- if (allow)
- {
- var eventData = m_NavigationState.eventData;
- if (eventData == null)
- {
- eventData = new ExtendedAxisEventData(eventSystem);
- m_NavigationState.eventData = eventData;
- }
- eventData.Reset();
-
- eventData.moveVector = moveVector;
- eventData.moveDir = moveDirection;
-
- if (IsMoveAllowed(eventData))
- {
- ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, eventData, ExecuteEvents.moveHandler);
- usedSelectionChange = eventData.used;
-
- m_NavigationState.consecutiveMoveCount = m_NavigationState.consecutiveMoveCount + 1;
- m_NavigationState.lastMoveTime = time;
- m_NavigationState.lastMoveDirection = moveDirection;
- }
- }
- }
- else
- m_NavigationState.consecutiveMoveCount = 0;
- }
- else
- {
- m_NavigationState.consecutiveMoveCount = 0;
- }
-
- // Process submit and cancel events.
- if (!usedSelectionChange && eventSystem.currentSelectedGameObject != null)
- {
- // NOTE: Whereas we use callbacks for the other actions, we rely on WasPressedThisFrame() for
- // submit and cancel. This makes their behavior inconsistent with pointer click behavior where
- // a click will register on button *up*, but consistent with how other UI systems work where
- // click occurs on key press. This nuance in behavior becomes important in combination with
- // action enable/disable changes in response to submit or cancel. We react to button *down*
- // instead of *up*, so button *up* will come in *after* we have applied the state change.
- var submitAction = m_SubmitAction?.action;
- var cancelAction = m_CancelAction?.action;
-
- var data = GetBaseEventData();
- if (cancelAction != null && cancelAction.WasPressedThisFrame())
- ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler);
- if (!data.used && submitAction != null && submitAction.WasPressedThisFrame())
- ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler);
- }
- }
-
- private bool IsMoveAllowed(AxisEventData eventData)
- {
- if (m_LocalMultiPlayerRoot == null)
- return true;
-
- if (eventSystem.currentSelectedGameObject == null)
- return true;
-
- var selectable = eventSystem.currentSelectedGameObject.GetComponent<Selectable>();
-
- if (selectable == null)
- return true;
-
- Selectable navigationTarget = null;
- switch (eventData.moveDir)
- {
- case MoveDirection.Right:
- navigationTarget = selectable.FindSelectableOnRight();
- break;
-
- case MoveDirection.Up:
- navigationTarget = selectable.FindSelectableOnUp();
- break;
-
- case MoveDirection.Left:
- navigationTarget = selectable.FindSelectableOnLeft();
- break;
-
- case MoveDirection.Down:
- navigationTarget = selectable.FindSelectableOnDown();
- break;
- }
-
- if (navigationTarget == null)
- return true;
-
- return navigationTarget.transform.IsChildOf(m_LocalMultiPlayerRoot.transform);
- }
-
- [FormerlySerializedAs("m_RepeatDelay")]
- [Tooltip("The Initial delay (in seconds) between an initial move action and a repeated move action.")]
- [SerializeField]
- private float m_MoveRepeatDelay = 0.5f;
-
- [FormerlySerializedAs("m_RepeatRate")]
- [Tooltip("The speed (in seconds) that the move action repeats itself once repeating (max 1 per frame).")]
- [SerializeField]
- private float m_MoveRepeatRate = 0.1f;
-
- [Tooltip("Scales the Eventsystem.DragThreshold, for tracked devices, to make selection easier.")]
- // Hide this while we still have to figure out what to do with this.
- private float m_TrackedDeviceDragThresholdMultiplier = 2.0f;
-
- [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.")]
- [SerializeField]
- private Transform m_XRTrackingOrigin;
-
- /// <summary>
- /// Delay in seconds between an initial move action and a repeated move action while <see cref="move"/> is actuated.
- /// </summary>
- /// <remarks>
- /// While <see cref="move"/> is being held down, the input module will first wait for <see cref="moveRepeatDelay"/> seconds
- /// after the first actuation of <see cref="move"/> and then trigger a move event every <see cref="moveRepeatRate"/> seconds.
- /// </remarks>
- /// <seealso cref="moveRepeatRate"/>
- /// <seealso cref="AxisEventData"/>
- /// <see cref="move"/>
- public float moveRepeatDelay
- {
- get => m_MoveRepeatDelay;
- set => m_MoveRepeatDelay = value;
- }
-
- /// <summary>
- /// Delay in seconds between repeated move actions while <see cref="move"/> is actuated.
- /// </summary>
- /// <remarks>
- /// While <see cref="move"/> is being held down, the input module will first wait for <see cref="moveRepeatDelay"/> seconds
- /// after the first actuation of <see cref="move"/> and then trigger a move event every <see cref="moveRepeatRate"/> seconds.
- ///
- /// Note that a maximum of one <see cref="AxisEventData"/> will be sent per frame. This means that even if multiple time
- /// increments of the repeat delay have passed since the last update, only one move repeat event will be generated.
- /// </remarks>
- /// <seealso cref="moveRepeatDelay"/>
- /// <seealso cref="AxisEventData"/>
- /// <see cref="move"/>
- public float moveRepeatRate
- {
- get => m_MoveRepeatRate;
- set => m_MoveRepeatRate = value;
- }
-
- private bool explictlyIgnoreFocus => InputSystem.settings.backgroundBehavior == InputSettings.BackgroundBehavior.IgnoreFocus;
-
- private bool shouldIgnoreFocus
- {
- // By default, key this on whether running the background is enabled or not. Rationale is that
- // if running in the background is enabled, we already have rules in place what kind of input
- // is allowed through and what isn't. And for the input that *IS* allowed through, the UI should
- // react.
- get => explictlyIgnoreFocus || InputRuntime.s_Instance.runInBackground;
- }
-
- [Obsolete("'repeatRate' has been obsoleted; use 'moveRepeatRate' instead. (UnityUpgradable) -> moveRepeatRate", false)]
- public float repeatRate
- {
- get => moveRepeatRate;
- set => moveRepeatRate = value;
- }
-
- [Obsolete("'repeatDelay' has been obsoleted; use 'moveRepeatDelay' instead. (UnityUpgradable) -> moveRepeatDelay", false)]
- public float repeatDelay
- {
- get => moveRepeatDelay;
- set => moveRepeatDelay = value;
- }
-
- /// <summary>
- /// A <see cref="Transform"/> representing the real world origin for tracking devices.
- /// This is used to convert real world positions and rotations for <see cref="UIPointerType.Tracked"/> pointers into Unity's global space.
- /// When using the XR Interaction Toolkit, this should be pointing to the XR Rig's Transform.
- /// </summary>
- /// <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>
- public Transform xrTrackingOrigin
- {
- get => m_XRTrackingOrigin;
- set => m_XRTrackingOrigin = value;
- }
-
- /// <summary>
- /// Scales the drag threshold of <c>EventSystem</c> for tracked devices to make selection easier.
- /// </summary>
- public float trackedDeviceDragThresholdMultiplier
- {
- get => m_TrackedDeviceDragThresholdMultiplier;
- set => m_TrackedDeviceDragThresholdMultiplier = value;
- }
-
- private void SwapAction(ref InputActionReference property, InputActionReference newValue, bool actionsHooked, Action<InputAction.CallbackContext> actionCallback)
- {
- if (property == newValue || (property != null && newValue != null && property.action == newValue.action))
- return;
-
- if (property != null && actionCallback != null && actionsHooked)
- {
- property.action.performed -= actionCallback;
- property.action.canceled -= actionCallback;
- }
-
- var oldActionNull = property?.action == null;
- var oldActionEnabled = property?.action != null && property.action.enabled;
-
- TryDisableInputAction(property);
- property = newValue;
-
- #if DEBUG
- // We source inputs from arbitrary pointers through a set of pointer-related actions (point, click, etc). This means that in any frame,
- // multiple pointers may pipe input through to the same action and we do not want the disambiguation code in InputActionState.ShouldIgnoreControlStateChange()
- // to prevent input from getting to us. Thus, these actions should generally be set to InputActionType.PassThrough.
- //
- // We treat navigation actions differently as there is only a single NavigationModel for the UI that all navigation input feeds into.
- // Thus, those actions should be configured with disambiguation active (i.e. Move should be a Value action and Submit and Cancel should
- // be Button actions). This is especially important for Submit and Cancel as we get proper press and release action this way.
- if (newValue != null && newValue.action != null && newValue.action.type != InputActionType.PassThrough && !IsNavigationAction(newValue))
- {
- 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 "
- + $"input from multiple pointers (action {newValue.action} is set to {newValue.action.type})", this);
- }
- #endif
-
- if (newValue?.action != null && actionCallback != null && actionsHooked)
- {
- property.action.performed += actionCallback;
- property.action.canceled += actionCallback;
- }
-
- if (isActiveAndEnabled && newValue?.action != null && (oldActionEnabled || oldActionNull))
- EnableInputAction(property);
- }
-
- #if DEBUG
- private bool IsNavigationAction(InputActionReference reference)
- {
- return reference == m_SubmitAction || reference == m_CancelAction || reference == m_MoveAction;
- }
-
- #endif
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <see cref="Vector2"/> 2D screen position
- /// used as a cursor for pointing at UI elements.
- /// </summary>
- /// <remarks>
- /// The values read from this action determine <see cref="PointerEventData.position"/> and <see cref="PointerEventData.delta"/>.
- ///
- /// Together with <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
- /// <see cref="scrollWheel"/>, this forms the basis for pointer-type UI input.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector2"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("Point");
- ///
- /// pointAction.AddBinding("<Mouse>/position");
- /// pointAction.AddBinding("<Touchscreen>/touch*/position");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
- /// InputActionReference.Create(pointAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="leftClick"/>
- /// <seealso cref="rightClick"/>
- /// <seealso cref="middleClick"/>
- /// <seealso cref="scrollWheel"/>
- public InputActionReference point
- {
- get => m_PointAction;
- set => SwapAction(ref m_PointAction, value, m_ActionsHooked, m_OnPointDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>Vector2</c> scroll wheel value
- /// used for sending <see cref="PointerEventData"/> events.
- /// </summary>
- /// <remarks>
- /// The values read from this action determine <see cref="PointerEventData.scrollDelta"/>.
- ///
- /// Together with <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
- /// <see cref="point"/>, this forms the basis for pointer-type UI input.
- ///
- /// Note that the action is optional. A pointer is fully functional with just <see cref="point"/>
- /// and <see cref="leftClick"/> alone.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector2"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("scroll");
- /// var scrollAction = map.AddAction("scroll");
- ///
- /// pointAction.AddBinding("<Mouse>/position");
- /// pointAction.AddBinding("<Touchscreen>/touch*/position");
- ///
- /// scrollAction.AddBinding("<Mouse>/scroll");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
- /// InputActionReference.Create(pointAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).scrollWheel =
- /// InputActionReference.Create(scrollAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="leftClick"/>
- /// <seealso cref="rightClick"/>
- /// <seealso cref="middleClick"/>
- /// <seealso cref="point"/>
- public InputActionReference scrollWheel
- {
- get => m_ScrollWheelAction;
- set => SwapAction(ref m_ScrollWheelAction, value, m_ActionsHooked, m_OnScrollWheelDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines
- /// whether the left button of a pointer is pressed.
- /// </summary>
- /// <remarks>
- /// Clicks on this button will use <see cref="PointerEventData.InputButton.Left"/> for <see cref="PointerEventData.button"/>.
- ///
- /// Together with <see cref="point"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
- /// <see cref="scrollWheel"/>, this forms the basis for pointer-type UI input.
- ///
- /// Note that together with <see cref="point"/>, this action is necessary for a pointer to be functional. The other clicks
- /// and <see cref="scrollWheel"/> are optional, however.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Button"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("scroll");
- /// var clickAction = map.AddAction("click");
- ///
- /// pointAction.AddBinding("<Mouse>/position");
- /// pointAction.AddBinding("<Touchscreen>/touch*/position");
- ///
- /// clickAction.AddBinding("<Mouse>/leftButton");
- /// clickAction.AddBinding("<Touchscreen>/touch*/press");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
- /// InputActionReference.Create(pointAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
- /// InputActionReference.Create(clickAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="rightClick"/>
- /// <seealso cref="middleClick"/>
- /// <seealso cref="scrollWheel"/>
- /// <seealso cref="point"/>
- public InputActionReference leftClick
- {
- get => m_LeftClickAction;
- set => SwapAction(ref m_LeftClickAction, value, m_ActionsHooked, m_OnLeftClickDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines
- /// whether the middle button of a pointer is pressed.
- /// </summary>
- /// <remarks>
- /// Clicks on this button will use <see cref="PointerEventData.InputButton.Middle"/> for <see cref="PointerEventData.button"/>.
- ///
- /// Together with <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="scrollWheel"/>, and
- /// <see cref="point"/>, this forms the basis for pointer-type UI input.
- ///
- /// Note that the action is optional. A pointer is fully functional with just <see cref="point"/>
- /// and <see cref="leftClick"/> alone.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Button"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("scroll");
- /// var leftClickAction = map.AddAction("leftClick");
- /// var middleClickAction = map.AddAction("middleClick");
- ///
- /// pointAction.AddBinding("<Mouse>/position");
- /// pointAction.AddBinding("<Touchscreen>/touch*/position");
- ///
- /// leftClickAction.AddBinding("<Mouse>/leftButton");
- /// leftClickAction.AddBinding("<Touchscreen>/touch*/press");
- ///
- /// middleClickAction.AddBinding("<Mouse>/middleButton");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
- /// InputActionReference.Create(pointAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
- /// InputActionReference.Create(leftClickAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).middleClick =
- /// InputActionReference.Create(middleClickAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="leftClick"/>
- /// <seealso cref="rightClick"/>
- /// <seealso cref="scrollWheel"/>
- /// <seealso cref="point"/>
- public InputActionReference middleClick
- {
- get => m_MiddleClickAction;
- set => SwapAction(ref m_MiddleClickAction, value, m_ActionsHooked, m_OnMiddleClickDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>float"</c> button value that determines
- /// whether the right button of a pointer is pressed.
- /// </summary>
- /// <remarks>
- /// Clicks on this button will use <see cref="PointerEventData.InputButton.Right"/> for <see cref="PointerEventData.button"/>.
- ///
- /// Together with <see cref="leftClick"/>, <see cref="middleClick"/>, <see cref="scrollWheel"/>, and
- /// <see cref="point"/>, this forms the basis for pointer-type UI input.
- ///
- /// Note that the action is optional. A pointer is fully functional with just <see cref="point"/>
- /// and <see cref="leftClick"/> alone.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Button"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("scroll");
- /// var leftClickAction = map.AddAction("leftClick");
- /// var rightClickAction = map.AddAction("rightClick");
- ///
- /// pointAction.AddBinding("<Mouse>/position");
- /// pointAction.AddBinding("<Touchscreen>/touch*/position");
- ///
- /// leftClickAction.AddBinding("<Mouse>/leftButton");
- /// leftClickAction.AddBinding("<Touchscreen>/touch*/press");
- ///
- /// rightClickAction.AddBinding("<Mouse>/rightButton");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).point =
- /// InputActionReference.Create(pointAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
- /// InputActionReference.Create(leftClickAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).rightClick =
- /// InputActionReference.Create(rightClickAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="leftClick"/>
- /// <seealso cref="middleClick"/>
- /// <seealso cref="scrollWheel"/>
- /// <seealso cref="point"/>
- public InputActionReference rightClick
- {
- get => m_RightClickAction;
- set => SwapAction(ref m_RightClickAction, value, m_ActionsHooked, m_OnRightClickDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>Vector2</c> 2D motion vector
- /// used for sending <see cref="AxisEventData"/> navigation events.
- /// </summary>
- /// <remarks>
- /// The events generated from this input will be received by <see cref="IMoveHandler.OnMove"/>.
- ///
- /// This action together with <see cref="submit"/> and <see cref="cancel"/> form the sources for navigation-style
- /// UI input.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector2"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("move");
- /// var submitAction = map.AddAction("submit");
- /// var cancelAction = map.AddAction("cancel");
- ///
- /// moveAction.AddBinding("<Gamepad>/*stick");
- /// moveAction.AddBinding("<Gamepad>/dpad");
- /// submitAction.AddBinding("<Gamepad>/buttonSouth");
- /// cancelAction.AddBinding("<Gamepad>/buttonEast");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).move =
- /// InputActionReference.Create(moveAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).submit =
- /// InputActionReference.Create(submitAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).cancelAction =
- /// InputActionReference.Create(cancelAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="submit"/>
- /// <seealso cref="cancel"/>
- public InputActionReference move
- {
- get => m_MoveAction;
- set => SwapAction(ref m_MoveAction, value, m_ActionsHooked, m_OnMoveDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines when <c>ISubmitHandler</c>
- /// is triggered.
- /// </summary>
- /// <remarks>
- /// The events generated from this input will be received by <see cref="ISubmitHandler"/>.
- ///
- /// This action together with <see cref="move"/> and <see cref="cancel"/> form the sources for navigation-style
- /// UI input.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.Button"/>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("move");
- /// var submitAction = map.AddAction("submit");
- /// var cancelAction = map.AddAction("cancel");
- ///
- /// moveAction.AddBinding("<Gamepad>/*stick");
- /// moveAction.AddBinding("<Gamepad>/dpad");
- /// submitAction.AddBinding("<Gamepad>/buttonSouth");
- /// cancelAction.AddBinding("<Gamepad>/buttonEast");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).move =
- /// InputActionReference.Create(moveAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).submit =
- /// InputActionReference.Create(submitAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).cancelAction =
- /// InputActionReference.Create(cancelAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="move"/>
- /// <seealso cref="cancel"/>
- public InputActionReference submit
- {
- get => m_SubmitAction;
- set => SwapAction(ref m_SubmitAction, value, m_ActionsHooked, null);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>float</c> button value that determines when <c>ICancelHandler</c>
- /// is triggered.
- /// </summary>
- /// <remarks>
- /// The events generated from this input will be received by <see cref="ICancelHandler"/>.
- ///
- /// This action together with <see cref="move"/> and <see cref="submit"/> form the sources for navigation-style
- /// UI input.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.Button"/>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var pointAction = map.AddAction("move");
- /// var submitAction = map.AddAction("submit");
- /// var cancelAction = map.AddAction("cancel");
- ///
- /// moveAction.AddBinding("<Gamepad>/*stick");
- /// moveAction.AddBinding("<Gamepad>/dpad");
- /// submitAction.AddBinding("<Gamepad>/buttonSouth");
- /// cancelAction.AddBinding("<Gamepad>/buttonEast");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).move =
- /// InputActionReference.Create(moveAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).submit =
- /// InputActionReference.Create(submitAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).cancelAction =
- /// InputActionReference.Create(cancelAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="move"/>
- /// <seealso cref="submit"/>
- public InputActionReference cancel
- {
- get => m_CancelAction;
- set => SwapAction(ref m_CancelAction, value, m_ActionsHooked, null);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>Quaternion</c> value reflecting the orientation of <see cref="TrackedDevice"/>s.
- /// In combination with <see cref="trackedDevicePosition"/>, this is used to determine the transform of tracked devices from which
- /// to raycast into the UI scene.
- /// </summary>
- /// <remarks>
- /// <see cref="trackedDeviceOrientation"/> and <see cref="trackedDevicePosition"/> together replace <see cref="point"/> for
- /// UI input from <see cref="TrackedDevice"/>. Other than that, UI input for tracked devices is no different from "normal"
- /// pointer-type input. This means that <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
- /// <see cref="scrollWheel"/> can all be used for tracked device input like for regular pointer input.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Quaternion"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var positionAction = map.AddAction("position");
- /// var orientationAction = map.AddAction("orientation");
- /// var clickAction = map.AddAction("click");
- ///
- /// positionAction.AddBinding("<TrackedDevice>/devicePosition");
- /// orientationAction.AddBinding("<TrackedDevice>/deviceRotation");
- /// clickAction.AddBinding("<TrackedDevice>/trigger");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDevicePosition =
- /// InputActionReference.Create(positionAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDeviceOrientation =
- /// InputActionReference.Create(orientationAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
- /// InputActionReference.Create(clickAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="trackedDevicePosition"/>
- public InputActionReference trackedDeviceOrientation
- {
- get => m_TrackedDeviceOrientationAction;
- set => SwapAction(ref m_TrackedDeviceOrientationAction, value, m_ActionsHooked, m_OnTrackedDeviceOrientationDelegate);
- }
-
- /// <summary>
- /// An <see cref="InputAction"/> delivering a <c>Vector3</c> value reflecting the position of <see cref="TrackedDevice"/>s.
- /// In combination with <see cref="trackedDeviceOrientation"/>, this is used to determine the transform of tracked devices from which
- /// to raycast into the UI scene.
- /// </summary>
- /// <remarks>
- /// <see cref="trackedDeviceOrientation"/> and <see cref="trackedDevicePosition"/> together replace <see cref="point"/> for
- /// UI input from <see cref="TrackedDevice"/>. Other than that, UI input for tracked devices is no different from "normal"
- /// pointer-type input. This means that <see cref="leftClick"/>, <see cref="rightClick"/>, <see cref="middleClick"/>, and
- /// <see cref="scrollWheel"/> can all be used for tracked device input like for regular pointer input.
- ///
- /// This action should have its <see cref="InputAction.type"/> set to <see cref="InputActionType.PassThrough"/> and its
- /// <see cref="InputAction.expectedControlType"/> set to <c>"Vector3"</c>.
- ///
- /// <example>
- /// <code>
- /// var asset = ScriptableObject.Create<InputActionAsset>();
- /// var map = asset.AddActionMap("UI");
- /// var positionAction = map.AddAction("position");
- /// var orientationAction = map.AddAction("orientation");
- /// var clickAction = map.AddAction("click");
- ///
- /// positionAction.AddBinding("<TrackedDevice>/devicePosition");
- /// orientationAction.AddBinding("<TrackedDevice>/deviceRotation");
- /// clickAction.AddBinding("<TrackedDevice>/trigger");
- ///
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDevicePosition =
- /// InputActionReference.Create(positionAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).trackedDeviceOrientation =
- /// InputActionReference.Create(orientationAction);
- /// ((InputSystemUIInputModule)EventSystem.current.currentInputModule).leftClick =
- /// InputActionReference.Create(clickAction);
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="trackedDeviceOrientation"/>
- public InputActionReference trackedDevicePosition
- {
- get => m_TrackedDevicePositionAction;
- set => SwapAction(ref m_TrackedDevicePositionAction, value, m_ActionsHooked, m_OnTrackedDevicePositionDelegate);
- }
-
- /// <summary>
- /// Assigns default input actions asset and input actions, similar to how defaults are assigned when creating UI module in editor.
- /// Useful for creating <see cref="InputSystemUIInputModule"/> at runtime.
- /// </summary>
- /// <remarks>
- /// This instantiates <see cref="DefaultInputActions"/> and assigns it to <see cref="actionsAsset"/>. It also
- /// assigns all the various individual actions such as <see cref="point"/> and <see cref="leftClick"/>.
- ///
- /// Note that if an <c>InputSystemUIInputModule</c> component is programmatically added to a <c>GameObject</c>,
- /// it will automatically receive the default actions as part of its <c>OnEnable</c> method. Use <see cref="UnassignActions"/>
- /// to remove these assignments.
- ///
- /// <example>
- /// <code>
- /// var go = new GameObject();
- /// go.AddComponent<EventSystem>();
- ///
- /// // Adding the UI module like this will implicitly enable it and thus lead to
- /// // automatic assignment of the default input actions.
- /// var uiModule = go.AddComponent<InputSystemUIInputModule>();
- ///
- /// // Manually remove the default input actions.
- /// uiModule.UnassignActions();
- /// </code>
- /// </example>
- /// </remarks>
- /// <seealso cref="actionsAsset"/>
- /// <seealso cref="DefaultInputActions"/>
-
- private static DefaultInputActions defaultActions;
-
- public void AssignDefaultActions()
- {
- if (defaultActions == null)
- {
- defaultActions = new DefaultInputActions();
- }
- actionsAsset = defaultActions.asset;
- cancel = InputActionReference.Create(defaultActions.UI.Cancel);
- submit = InputActionReference.Create(defaultActions.UI.Submit);
- move = InputActionReference.Create(defaultActions.UI.Navigate);
- leftClick = InputActionReference.Create(defaultActions.UI.Click);
- rightClick = InputActionReference.Create(defaultActions.UI.RightClick);
- middleClick = InputActionReference.Create(defaultActions.UI.MiddleClick);
- point = InputActionReference.Create(defaultActions.UI.Point);
- scrollWheel = InputActionReference.Create(defaultActions.UI.ScrollWheel);
- trackedDeviceOrientation = InputActionReference.Create(defaultActions.UI.TrackedDeviceOrientation);
- trackedDevicePosition = InputActionReference.Create(defaultActions.UI.TrackedDevicePosition);
- }
-
- /// <summary>
- /// Remove all action assignments, that is <see cref="actionsAsset"/> as well as all individual
- /// actions such as <see cref="leftClick"/>.
- /// </summary>
- /// <remarks>
- /// If the current actions were enabled by the UI input module, they will be disabled in the process.
- /// </remarks>
- /// <seealso cref="AssignDefaultActions"/>
- public void UnassignActions()
- {
- defaultActions?.Dispose();
- defaultActions = default;
- actionsAsset = default;
- cancel = default;
- submit = default;
- move = default;
- leftClick = default;
- rightClick = default;
- middleClick = default;
- point = default;
- scrollWheel = default;
- trackedDeviceOrientation = default;
- trackedDevicePosition = default;
- }
-
- [Obsolete("'trackedDeviceSelect' has been obsoleted; use 'leftClick' instead.", true)]
- public InputActionReference trackedDeviceSelect
- {
- get => throw new InvalidOperationException();
- set => throw new InvalidOperationException();
- }
-
- #if UNITY_EDITOR
- protected override void Reset()
- {
- base.Reset();
-
- var asset = (InputActionAsset)AssetDatabase.LoadAssetAtPath(
- UnityEngine.InputSystem.Editor.PlayerInputEditor.kDefaultInputActionsAssetPath,
- typeof(InputActionAsset));
- // Setting default asset and actions when creating via inspector
- Editor.InputSystemUIInputModuleEditor.ReassignActions(this, asset);
- }
-
- #endif
-
- protected override void Awake()
- {
- base.Awake();
-
- m_NavigationState.Reset();
- }
-
- protected override void OnDestroy()
- {
- base.OnDestroy();
-
- UnhookActions();
- }
-
- protected override void OnEnable()
- {
- base.OnEnable();
-
- if (m_OnControlsChangedDelegate == null)
- m_OnControlsChangedDelegate = OnControlsChanged;
- InputActionState.s_GlobalState.onActionControlsChanged.AddCallback(m_OnControlsChangedDelegate);
-
- if (HasNoActions())
- AssignDefaultActions();
-
- ResetPointers();
-
- HookActions();
- EnableAllActions();
- }
-
- protected override void OnDisable()
- {
- ResetPointers();
-
- InputActionState.s_GlobalState.onActionControlsChanged.RemoveCallback(m_OnControlsChangedDelegate);
-
- DisableAllActions();
- UnhookActions();
-
- base.OnDisable();
- }
-
- private void ResetPointers()
- {
- var numPointers = m_PointerStates.length;
- for (var i = 0; i < numPointers; ++i)
- SendPointerExitEventsAndRemovePointer(0);
-
- m_CurrentPointerId = -1;
- m_CurrentPointerIndex = -1;
- m_CurrentPointerType = UIPointerType.None;
- }
-
- private bool HasNoActions()
- {
- if (m_ActionsAsset != null)
- return false;
-
- return m_PointAction?.action == null
- && m_LeftClickAction?.action == null
- && m_RightClickAction?.action == null
- && m_MiddleClickAction?.action == null
- && m_SubmitAction?.action == null
- && m_CancelAction?.action == null
- && m_ScrollWheelAction?.action == null
- && m_TrackedDeviceOrientationAction?.action == null
- && m_TrackedDevicePositionAction?.action == null;
- }
-
- private void EnableAllActions()
- {
- EnableInputAction(m_PointAction);
- EnableInputAction(m_LeftClickAction);
- EnableInputAction(m_RightClickAction);
- EnableInputAction(m_MiddleClickAction);
- EnableInputAction(m_MoveAction);
- EnableInputAction(m_SubmitAction);
- EnableInputAction(m_CancelAction);
- EnableInputAction(m_ScrollWheelAction);
- EnableInputAction(m_TrackedDeviceOrientationAction);
- EnableInputAction(m_TrackedDevicePositionAction);
- }
-
- private void DisableAllActions()
- {
- TryDisableInputAction(m_PointAction, true);
- TryDisableInputAction(m_LeftClickAction, true);
- TryDisableInputAction(m_RightClickAction, true);
- TryDisableInputAction(m_MiddleClickAction, true);
- TryDisableInputAction(m_MoveAction, true);
- TryDisableInputAction(m_SubmitAction, true);
- TryDisableInputAction(m_CancelAction, true);
- TryDisableInputAction(m_ScrollWheelAction, true);
- TryDisableInputAction(m_TrackedDeviceOrientationAction, true);
- TryDisableInputAction(m_TrackedDevicePositionAction, true);
- }
-
- private void EnableInputAction(InputActionReference inputActionReference)
- {
- var action = inputActionReference?.action;
- if (action == null)
- return;
-
- if (s_InputActionReferenceCounts.TryGetValue(action, out var referenceState))
- {
- referenceState.refCount++;
- s_InputActionReferenceCounts[action] = referenceState;
- }
- else
- {
- // if the action is already enabled but its reference count is zero then it was enabled by
- // something outside the input module and the input module should never disable it.
- referenceState = new InputActionReferenceState {refCount = 1, enabledByInputModule = !action.enabled};
- s_InputActionReferenceCounts.Add(action, referenceState);
- }
-
- action.Enable();
- }
-
- private void TryDisableInputAction(InputActionReference inputActionReference, bool isComponentDisabling = false)
- {
- var action = inputActionReference?.action;
- if (action == null)
- return;
-
- // Don't decrement refCount when we were not responsible for incrementing it.
- // I.e. when we were not enabled yet. When OnDisabled is called, isActiveAndEnabled will
- // already have been set to false. In that case we pass isComponentDisabling to check if we
- // came from OnDisabled and therefore need to allow disabling.
- if (!isActiveAndEnabled && !isComponentDisabling)
- return;
-
- if (!s_InputActionReferenceCounts.TryGetValue(action, out var referenceState))
- return;
-
- if (referenceState.refCount - 1 == 0 && referenceState.enabledByInputModule)
- {
- action.Disable();
- s_InputActionReferenceCounts.Remove(action);
- return;
- }
-
- referenceState.refCount--;
- s_InputActionReferenceCounts[action] = referenceState;
- }
-
- private int GetPointerStateIndexFor(int pointerOrTouchId)
- {
- if (pointerOrTouchId == m_CurrentPointerId)
- return m_CurrentPointerIndex;
-
- for (var i = 0; i < m_PointerIds.length; ++i)
- if (m_PointerIds[i] == pointerOrTouchId)
- return i;
-
- // Search for Device or Touch Ids as a fallback
- for (var i = 0; i < m_PointerStates.length; ++i)
- {
- var eventData = m_PointerStates[i].eventData;
- if (eventData.touchId == pointerOrTouchId || (eventData.touchId != 0 && eventData.device.deviceId == pointerOrTouchId))
- return i;
- }
-
- return -1;
- }
-
- private ref PointerModel GetPointerStateForIndex(int index)
- {
- if (index == 0)
- return ref m_PointerStates.firstValue;
- return ref m_PointerStates.additionalValues[index - 1];
- }
-
- private int GetDisplayIndexFor(InputControl control)
- {
- int displayIndex = 0;
- if (control.device is Pointer pointerCast)
- {
- displayIndex = pointerCast.displayIndex.ReadValue();
- Debug.Assert(displayIndex <= byte.MaxValue, "Display index was larger than expected");
- }
- return displayIndex;
- }
-
- private int GetPointerStateIndexFor(ref InputAction.CallbackContext context)
- {
- if (CheckForRemovedDevice(ref context))
- return -1;
-
- var phase = context.phase;
- return GetPointerStateIndexFor(context.control, createIfNotExists: phase != InputActionPhase.Canceled);
- }
-
- // This is the key method for determining which pointer a particular input is associated with.
- // The principal determinant is the device that is sending the input which, in general, is expected
- // to be a Pointer (Mouse, Pen, Touchscreen) or TrackedDevice.
- //
- // Note, however, that the input is not guaranteed to even come from a pointer-like device. One can
- // bind the space key to a left click, for example. As long as we have an active pointer that can
- // deliver position input, we accept that setup and treat pressing the space key the same as pressing
- // the left button input on the respective pointer.
- //
- // Quite a lot going on in this method but we're dealing with three different UI interaction paradigms
- // here which we all support from a single input path and allow seamless switching between.
- private int GetPointerStateIndexFor(InputControl control, bool createIfNotExists = true)
- {
- Debug.Assert(control != null, "Control must not be null");
-
- ////REVIEW: Any way we can cut down on the hops all over memory that we're doing here?
- var device = control.device;
-
- ////TODO: We're repeatedly inspecting the control setup here. Do this once and only redo it if the control setup changes.
-
- ////REVIEW: It seems wrong that we are picking up an input here that is *NOT* reflected in our actions. We just end
- //// up reading a touchId control implicitly instead of allowing actions to deliver IDs to us. On the other hand,
- //// making that setup explicit in actions may be quite awkward and not nearly as robust.
- // Determine the pointer (and touch) ID. We default the pointer ID to the device
- // ID of the InputDevice.
- var controlParent = control.parent;
- var touchControlIndex = m_PointerTouchControls.IndexOfReference(controlParent);
- if (touchControlIndex != -1)
- {
- // For touches, we cache a reference to the control of a pointer so that we don't
- // have to continuously do ReadValue() on the touch ID control.
- m_CurrentPointerId = m_PointerIds[touchControlIndex];
- m_CurrentPointerIndex = touchControlIndex;
- m_CurrentPointerType = UIPointerType.Touch;
-
- return touchControlIndex;
- }
-
- var pointerId = device.deviceId;
- var touchId = 0;
- var touchPosition = Vector2.zero;
-
- // Need to check if it's a touch so that we get a correct pointerId.
- if (controlParent is TouchControl touchControl)
- {
- touchId = touchControl.touchId.value;
- touchPosition = touchControl.position.value;
- }
- // Could be it's a toplevel control on Touchscreen (like "<Touchscreen>/position"). In that case,
- // read the touch ID from primaryTouch.
- else if (controlParent is Touchscreen touchscreen)
- {
- touchId = touchscreen.primaryTouch.touchId.value;
- touchPosition = touchscreen.primaryTouch.position.value;
- }
-
- int displayIndex = GetDisplayIndexFor(control);
-
- if (touchId != 0)
- pointerId = ExtendedPointerEventData.MakePointerIdForTouch(pointerId, touchId);
-
- // Early out if it's the last used pointer.
- // NOTE: Can't just compare by device here because of touchscreens potentially having multiple associated pointers.
- if (m_CurrentPointerId == pointerId)
- return m_CurrentPointerIndex;
-
- // Search m_PointerIds for an existing entry.
- // NOTE: This is a linear search but m_PointerIds is only IDs and the number of concurrent pointers
- // should be very low at any one point (in fact, we don't generally expect to have more than one
- // which is why we are using InlinedArrays).
- if (touchId == 0) // Not necessary for touches; see above.
- {
- for (var i = 0; i < m_PointerIds.length; i++)
- {
- if (m_PointerIds[i] == pointerId)
- {
- // Existing entry found. Make it the current pointer.
- m_CurrentPointerId = pointerId;
- m_CurrentPointerIndex = i;
- m_CurrentPointerType = m_PointerStates[i].pointerType;
- return i;
- }
- }
- }
-
- if (!createIfNotExists)
- return -1;
-
- // Determine pointer type.
- var pointerType = UIPointerType.None;
- if (touchId != 0)
- pointerType = UIPointerType.Touch;
- else if (HaveControlForDevice(device, point))
- pointerType = UIPointerType.MouseOrPen;
- else if (HaveControlForDevice(device, trackedDevicePosition))
- pointerType = UIPointerType.Tracked;
-
- ////REVIEW: For touch, probably makes sense to force-ignore any input other than from primaryTouch.
- // If the behavior is SingleUnifiedPointer, we only ever create a single pointer state
- // and use that for all pointer input that is coming in.
- if ((m_PointerBehavior == UIPointerBehavior.SingleUnifiedPointer && pointerType != UIPointerType.None) ||
- (m_PointerBehavior == UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack && pointerType == UIPointerType.MouseOrPen))
- {
- if (m_CurrentPointerIndex == -1)
- {
- m_CurrentPointerIndex = AllocatePointer(pointerId, displayIndex, touchId, pointerType, control, device, touchId != 0 ? controlParent : null);
- }
- else
- {
- // Update pointer record to reflect current device. We know they're different because we checked
- // m_CurrentPointerId earlier in the method.
- // NOTE: This path may repeatedly switch the pointer type and ID on the same single event instance.
-
- ref var pointer = ref GetPointerStateForIndex(m_CurrentPointerIndex);
-
- var eventData = pointer.eventData;
- eventData.control = control;
- eventData.device = device;
- eventData.pointerType = pointerType;
- eventData.pointerId = pointerId;
- eventData.touchId = touchId;
- #if UNITY_2022_3_OR_NEWER
- eventData.displayIndex = displayIndex;
- #endif
-
- // Make sure these don't linger around when we switch to a different kind of pointer.
- eventData.trackedDeviceOrientation = default;
- eventData.trackedDevicePosition = default;
- }
-
- if (pointerType == UIPointerType.Touch)
- GetPointerStateForIndex(m_CurrentPointerIndex).screenPosition = touchPosition;
-
- m_CurrentPointerId = pointerId;
- m_CurrentPointerType = pointerType;
-
- return m_CurrentPointerIndex;
- }
-
- // No existing record for the device. Find out if the device has the ability to point at all.
- // If not, we need to use a pointer state from a different device (if present).
- var index = -1;
- if (pointerType != UIPointerType.None)
- {
- // Device has an associated position input. Create a new pointer record.
- index = AllocatePointer(pointerId, displayIndex, touchId, pointerType, control, device, touchId != 0 ? controlParent : null);
- }
- else
- {
- // Device has no associated position input. Find a pointer device to route the change into.
- // As a last resort, create a pointer without a position input.
-
- // If we have a current pointer, route the input into that. The majority of times we end
- // up in this branch, this should settle things.
- if (m_CurrentPointerId != -1)
- return m_CurrentPointerIndex;
-
- // NOTE: In most cases, we end up here when there is input on a non-pointer device bound to one of the pointer-related
- // actions before there is input from a pointer device. In this scenario, we don't have a pointer state allocated
- // for the device yet.
-
- // If we have anything bound to the `point` action, create a pointer for it.
- var pointControls = point?.action?.controls;
- var pointerDevice = pointControls.HasValue && pointControls.Value.Count > 0 ? pointControls.Value[0].device : null;
- if (pointerDevice != null && !(pointerDevice is Touchscreen)) // Touchscreen only temporarily allocate pointer states.
- {
- // Create MouseOrPen style pointer.
- index = AllocatePointer(pointerDevice.deviceId, displayIndex, 0, UIPointerType.MouseOrPen, pointControls.Value[0], pointerDevice);
- }
- else
- {
- // Do the same but look at the `position` action.
- var positionControls = trackedDevicePosition?.action?.controls;
- var trackedDevice = positionControls.HasValue && positionControls.Value.Count > 0
- ? positionControls.Value[0].device
- : null;
- if (trackedDevice != null)
- {
- // Create a Tracked style pointer.
- index = AllocatePointer(trackedDevice.deviceId, displayIndex, 0, UIPointerType.Tracked, positionControls.Value[0], trackedDevice);
- }
- else
- {
- // We got input from a non-pointer device and apparently there's no pointer we can route the
- // input into. Just create a pointer state for the device and leave it at that.
- index = AllocatePointer(pointerId, displayIndex, 0, UIPointerType.None, control, device);
- }
- }
- }
-
- if (pointerType == UIPointerType.Touch)
- GetPointerStateForIndex(index).screenPosition = touchPosition;
-
- m_CurrentPointerId = pointerId;
- m_CurrentPointerIndex = index;
- m_CurrentPointerType = pointerType;
-
- return index;
- }
-
- private int AllocatePointer(int pointerId, int displayIndex, int touchId, UIPointerType pointerType, InputControl control, InputDevice device, InputControl touchControl = null)
- {
- // Recover event instance from previous record.
- var eventData = default(ExtendedPointerEventData);
- if (m_PointerStates.Capacity > m_PointerStates.length)
- {
- if (m_PointerStates.length == 0)
- eventData = m_PointerStates.firstValue.eventData;
- else
- eventData = m_PointerStates.additionalValues[m_PointerStates.length - 1].eventData;
- }
-
- // Or allocate event.
- if (eventData == null)
- eventData = new ExtendedPointerEventData(eventSystem);
-
- eventData.pointerId = pointerId;
- #if UNITY_2022_3_OR_NEWER
- eventData.displayIndex = displayIndex;
- #endif
- eventData.touchId = touchId;
- eventData.pointerType = pointerType;
- eventData.control = control;
- eventData.device = device;
-
- // Allocate state.
- m_PointerIds.AppendWithCapacity(pointerId);
- m_PointerTouchControls.AppendWithCapacity(touchControl);
- return m_PointerStates.AppendWithCapacity(new PointerModel(eventData));
- }
-
- private void SendPointerExitEventsAndRemovePointer(int index)
- {
- var eventData = m_PointerStates[index].eventData;
- if (eventData.pointerEnter != null)
- ProcessPointerMovement(eventData, null);
-
- RemovePointerAtIndex(index);
- }
-
- private void RemovePointerAtIndex(int index)
- {
- Debug.Assert(m_PointerStates[index].eventData.pointerEnter == null, "Pointer should have exited all objects before being removed");
-
- // Retain event data so that we can reuse the event the next time we allocate a PointerModel record.
- var eventData = m_PointerStates[index].eventData;
- Debug.Assert(eventData != null, "Pointer state should have an event instance!");
-
- // Update current pointer, if necessary.
- if (index == m_CurrentPointerIndex)
- {
- m_CurrentPointerId = -1;
- m_CurrentPointerIndex = -1;
- m_CurrentPointerType = default;
- }
- else if (m_CurrentPointerIndex == m_PointerIds.length - 1)
- {
- // We're about to move the last entry so update the index it will
- // be at.
- m_CurrentPointerIndex = index;
- }
-
- // Remove. Note that we may change the order of pointers here. This can save us needless copying
- // and m_CurrentPointerIndex should be the only index we get around for longer.
- m_PointerIds.RemoveAtByMovingTailWithCapacity(index);
- m_PointerTouchControls.RemoveAtByMovingTailWithCapacity(index);
- m_PointerStates.RemoveAtByMovingTailWithCapacity(index);
- Debug.Assert(m_PointerIds.length == m_PointerStates.length, "Pointer ID array should match state array in length");
-
- // Put event instance back in place at one past last entry of array (which we know we have
- // as we just erased one entry). This entry will be the next one that will be used when we
- // allocate a new entry.
-
- // Wipe the event.
- // NOTE: We only wipe properties here that contain reference data. The rest we rely on
- // the event handling code to initialize when using the event.
- eventData.hovered.Clear();
- eventData.device = null;
- eventData.pointerCurrentRaycast = default;
- eventData.pointerPressRaycast = default;
- eventData.pointerPress = default; // Twice to wipe lastPress, too.
- eventData.pointerPress = default;
- eventData.pointerDrag = default;
- eventData.pointerEnter = default;
- eventData.rawPointerPress = default;
-
- if (m_PointerStates.length == 0)
- m_PointerStates.firstValue.eventData = eventData;
- else
- m_PointerStates.additionalValues[m_PointerStates.length - 1].eventData = eventData;
- }
-
- // Remove any pointer that no longer has the ability to point.
- private void PurgeStalePointers()
- {
- for (var i = 0; i < m_PointerStates.length; ++i)
- {
- ref var state = ref GetPointerStateForIndex(i);
- var device = state.eventData.device;
- if (!device.added || // Check if device was removed altogether.
- (!HaveControlForDevice(device, point) &&
- !HaveControlForDevice(device, trackedDevicePosition) &&
- !HaveControlForDevice(device, trackedDeviceOrientation)))
- {
- SendPointerExitEventsAndRemovePointer(i);
- --i;
- }
- }
-
- m_NeedToPurgeStalePointers = false;
- }
-
- private static bool HaveControlForDevice(InputDevice device, InputActionReference actionReference)
- {
- var action = actionReference?.action;
- if (action == null)
- return false;
-
- var controls = action.controls;
- for (var i = 0; i < controls.Count; ++i)
- if (controls[i].device == device)
- return true;
-
- return false;
- }
-
- // The pointer actions we unfortunately cannot poll as we may be sourcing input from multiple pointers.
-
- private void OnPointCallback(InputAction.CallbackContext context)
- {
- // When a pointer is removed, there's like a non-zero coordinate on the position control and thus
- // we will see cancellations on the "Point" action. Ignore these as they provide no useful values
- // and we want to avoid doing a read of touch IDs in GetPointerStateFor() on an already removed
- // touchscreen.
- if (CheckForRemovedDevice(ref context) || context.canceled)
- return;
-
- var index = GetPointerStateIndexFor(context.control);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- state.screenPosition = context.ReadValue<Vector2>();
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- // NOTE: In the click events, we specifically react to the Canceled phase to make sure we do NOT perform
- // button *clicks* when an action resets. However, we still need to send pointer ups.
-
- private bool IgnoreNextClick(ref InputAction.CallbackContext context, bool wasPressed)
- {
- // If explicitly ignoring focus due to setting, never ignore clicks
- if (explictlyIgnoreFocus)
- return false;
- // If a currently active click is cancelled (by focus change), ignore next click if device cannot run in background.
- // This prevents the cancelled click event being registered when focus is returned i.e. if
- // the button was released while another window was focused.
- return context.canceled && !InputRuntime.s_Instance.isPlayerFocused && !context.control.device.canRunInBackground && wasPressed;
- }
-
- private void OnLeftClickCallback(InputAction.CallbackContext context)
- {
- var index = GetPointerStateIndexFor(ref context);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- bool wasPressed = state.leftButton.isPressed;
- state.leftButton.isPressed = context.ReadValueAsButton();
- state.changedThisFrame = true;
- if (IgnoreNextClick(ref context, wasPressed))
- state.leftButton.ignoreNextClick = true;
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- private void OnRightClickCallback(InputAction.CallbackContext context)
- {
- var index = GetPointerStateIndexFor(ref context);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- bool wasPressed = state.rightButton.isPressed;
- state.rightButton.isPressed = context.ReadValueAsButton();
- state.changedThisFrame = true;
- if (IgnoreNextClick(ref context, wasPressed))
- state.rightButton.ignoreNextClick = true;
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- private void OnMiddleClickCallback(InputAction.CallbackContext context)
- {
- var index = GetPointerStateIndexFor(ref context);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- bool wasPressed = state.middleButton.isPressed;
- state.middleButton.isPressed = context.ReadValueAsButton();
- state.changedThisFrame = true;
- if (IgnoreNextClick(ref context, wasPressed))
- state.middleButton.ignoreNextClick = true;
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- private bool CheckForRemovedDevice(ref InputAction.CallbackContext context)
- {
- // When a device is removed, we want to simply cancel ongoing pointer
- // operations. Most importantly, we want to prevent GetPointerStateFor()
- // doing ReadValue() on touch ID controls when a touchscreen has already
- // been removed.
- if (context.canceled && !context.control.device.added)
- {
- m_NeedToPurgeStalePointers = true;
- return true;
- }
- return false;
- }
-
- internal const float kPixelPerLine = 20;
-
- private void OnScrollCallback(InputAction.CallbackContext context)
- {
- var index = GetPointerStateIndexFor(ref context);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- // The old input system reported scroll deltas in lines, we report pixels.
- // Need to scale as the UI system expects lines.
- state.scrollDelta = context.ReadValue<Vector2>() * (1 / kPixelPerLine);
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- private void OnMoveCallback(InputAction.CallbackContext context)
- {
- ////REVIEW: should we poll this? or set the action to not be pass-through? (ps4 controller is spamming this action)
- m_NavigationState.move = context.ReadValue<Vector2>();
- }
-
- private void OnTrackedDeviceOrientationCallback(InputAction.CallbackContext context)
- {
- var index = GetPointerStateIndexFor(ref context);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- state.worldOrientation = context.ReadValue<Quaternion>();
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- private void OnTrackedDevicePositionCallback(InputAction.CallbackContext context)
- {
- var index = GetPointerStateIndexFor(ref context);
- if (index == -1)
- return;
-
- ref var state = ref GetPointerStateForIndex(index);
- state.worldPosition = context.ReadValue<Vector3>();
- #if UNITY_2022_3_OR_NEWER
- state.eventData.displayIndex = GetDisplayIndexFor(context.control);
- #endif
- }
-
- private void OnControlsChanged(object obj)
- {
- m_NeedToPurgeStalePointers = true;
- }
-
- private void FilterPointerStatesByType()
- {
- var pointerTypeToProcess = UIPointerType.None;
- // Read all pointers device states
- // Find first pointer that has changed this frame to be processed later
- for (var i = 0; i < m_PointerStates.length; ++i)
- {
- ref var state = ref GetPointerStateForIndex(i);
- state.eventData.ReadDeviceState();
- state.CopyTouchOrPenStateFrom(state.eventData);
- if (state.changedThisFrame && pointerTypeToProcess == UIPointerType.None)
- pointerTypeToProcess = state.pointerType;
- }
-
- // For SingleMouseOrPenButMultiTouchAndTrack, we keep a single pointer for mouse and pen but only for as
- // long as there is no touch or tracked input. If we get that kind, we remove the mouse/pen pointer.
- if (m_PointerBehavior == UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack && pointerTypeToProcess != UIPointerType.None)
- {
- // var pointerTypeToProcess = m_PointerStates.firstValue.pointerType;
- if (pointerTypeToProcess == UIPointerType.MouseOrPen)
- {
- // We have input on a mouse or pen. Kill all touch and tracked pointers we may have.
- for (var i = 0; i < m_PointerStates.length; ++i)
- {
- if (m_PointerStates[i].pointerType != UIPointerType.MouseOrPen)
- {
- SendPointerExitEventsAndRemovePointer(i);
- --i;
- }
- }
- }
- else
- {
- // We have touch or tracked input. Kill mouse/pen pointer, if we have it.
- for (var i = 0; i < m_PointerStates.length; ++i)
- {
- if (m_PointerStates[i].pointerType == UIPointerType.MouseOrPen)
- {
- SendPointerExitEventsAndRemovePointer(i);
- --i;
- }
- }
- }
- }
- }
-
- public override void Process()
- {
- if (m_NeedToPurgeStalePointers)
- PurgeStalePointers();
-
- // Reset devices of changes since we don't want to spool up changes once we gain focus.
- if (!eventSystem.isFocused && !shouldIgnoreFocus)
- {
- for (var i = 0; i < m_PointerStates.length; ++i)
- m_PointerStates[i].OnFrameFinished();
- }
- else
- {
- // Navigation input.
- ProcessNavigation(ref m_NavigationState);
-
- FilterPointerStatesByType();
-
- // Pointer input.
- for (var i = 0; i < m_PointerStates.length; i++)
- {
- ref var state = ref GetPointerStateForIndex(i);
-
- ProcessPointer(ref state);
-
- // If it's a touch and the touch has ended, release the pointer state.
- // NOTE: We defer this by one frame such that OnPointerUp happens in the frame of release
- // and OnPointerExit happens one frame later. This is so that IsPointerOverGameObject()
- // stays true for the touch in the frame of release (see UI_TouchPointersAreKeptForOneFrameAfterRelease).
- if (state.pointerType == UIPointerType.Touch && !state.leftButton.isPressed && !state.leftButton.wasReleasedThisFrame)
- {
- RemovePointerAtIndex(i);
- --i;
- continue;
- }
-
- state.OnFrameFinished();
- }
- }
- }
-
- #if UNITY_2021_1_OR_NEWER
- public override int ConvertUIToolkitPointerId(PointerEventData sourcePointerData)
- {
- // Case 1369081: when using SingleUnifiedPointer, the same (default) pointerId should be sent to UIToolkit
- // regardless of pointer type or finger id.
- if (m_PointerBehavior == UIPointerBehavior.SingleUnifiedPointer)
- return UIElements.PointerId.mousePointerId;
-
- return sourcePointerData is ExtendedPointerEventData ep
- ? ep.uiToolkitPointerId
- : base.ConvertUIToolkitPointerId(sourcePointerData);
- }
-
- #endif
-
- private void HookActions()
- {
- if (m_ActionsHooked)
- return;
-
- if (m_OnPointDelegate == null)
- m_OnPointDelegate = OnPointCallback;
- if (m_OnLeftClickDelegate == null)
- m_OnLeftClickDelegate = OnLeftClickCallback;
- if (m_OnRightClickDelegate == null)
- m_OnRightClickDelegate = OnRightClickCallback;
- if (m_OnMiddleClickDelegate == null)
- m_OnMiddleClickDelegate = OnMiddleClickCallback;
- if (m_OnScrollWheelDelegate == null)
- m_OnScrollWheelDelegate = OnScrollCallback;
- if (m_OnMoveDelegate == null)
- m_OnMoveDelegate = OnMoveCallback;
- if (m_OnTrackedDeviceOrientationDelegate == null)
- m_OnTrackedDeviceOrientationDelegate = OnTrackedDeviceOrientationCallback;
- if (m_OnTrackedDevicePositionDelegate == null)
- m_OnTrackedDevicePositionDelegate = OnTrackedDevicePositionCallback;
-
- SetActionCallbacks(true);
- }
-
- private void UnhookActions()
- {
- if (!m_ActionsHooked)
- return;
-
- SetActionCallbacks(false);
- }
-
- private void SetActionCallbacks(bool install)
- {
- m_ActionsHooked = install;
- SetActionCallback(m_PointAction, m_OnPointDelegate, install);
- SetActionCallback(m_MoveAction, m_OnMoveDelegate, install);
- SetActionCallback(m_LeftClickAction, m_OnLeftClickDelegate, install);
- SetActionCallback(m_RightClickAction, m_OnRightClickDelegate, install);
- SetActionCallback(m_MiddleClickAction, m_OnMiddleClickDelegate, install);
- SetActionCallback(m_ScrollWheelAction, m_OnScrollWheelDelegate, install);
- SetActionCallback(m_TrackedDeviceOrientationAction, m_OnTrackedDeviceOrientationDelegate, install);
- SetActionCallback(m_TrackedDevicePositionAction, m_OnTrackedDevicePositionDelegate, install);
- }
-
- private static void SetActionCallback(InputActionReference actionReference, Action<InputAction.CallbackContext> callback, bool install)
- {
- if (!install && callback == null)
- return;
-
- if (actionReference == null)
- return;
-
- var action = actionReference.action;
- if (action == null)
- return;
-
- if (install)
- {
- action.performed += callback;
- action.canceled += callback;
- }
- else
- {
- action.performed -= callback;
- action.canceled -= callback;
- }
- }
-
- private InputActionReference UpdateReferenceForNewAsset(InputActionReference actionReference)
- {
- var oldAction = actionReference?.action;
- if (oldAction == null)
- return null;
-
- var oldActionMap = oldAction.actionMap;
- Debug.Assert(oldActionMap != null, "Not expected to end up with a singleton action here");
-
- var newActionMap = m_ActionsAsset?.FindActionMap(oldActionMap.name);
- if (newActionMap == null)
- return null;
-
- var newAction = newActionMap.FindAction(oldAction.name);
- if (newAction == null)
- return null;
-
- return InputActionReference.Create(newAction);
- }
-
- public InputActionAsset actionsAsset
- {
- get => m_ActionsAsset;
- set
- {
- if (value != m_ActionsAsset)
- {
- UnhookActions();
-
- m_ActionsAsset = value;
-
- point = UpdateReferenceForNewAsset(point);
- move = UpdateReferenceForNewAsset(move);
- leftClick = UpdateReferenceForNewAsset(leftClick);
- rightClick = UpdateReferenceForNewAsset(rightClick);
- middleClick = UpdateReferenceForNewAsset(middleClick);
- scrollWheel = UpdateReferenceForNewAsset(scrollWheel);
- submit = UpdateReferenceForNewAsset(submit);
- cancel = UpdateReferenceForNewAsset(cancel);
- trackedDeviceOrientation = UpdateReferenceForNewAsset(trackedDeviceOrientation);
- trackedDevicePosition = UpdateReferenceForNewAsset(trackedDevicePosition);
-
- HookActions();
- }
- }
- }
-
- [SerializeField, HideInInspector] private InputActionAsset m_ActionsAsset;
- [SerializeField, HideInInspector] private InputActionReference m_PointAction;
- [SerializeField, HideInInspector] private InputActionReference m_MoveAction;
- [SerializeField, HideInInspector] private InputActionReference m_SubmitAction;
- [SerializeField, HideInInspector] private InputActionReference m_CancelAction;
- [SerializeField, HideInInspector] private InputActionReference m_LeftClickAction;
- [SerializeField, HideInInspector] private InputActionReference m_MiddleClickAction;
- [SerializeField, HideInInspector] private InputActionReference m_RightClickAction;
- [SerializeField, HideInInspector] private InputActionReference m_ScrollWheelAction;
- [SerializeField, HideInInspector] private InputActionReference m_TrackedDevicePositionAction;
- [SerializeField, HideInInspector] private InputActionReference m_TrackedDeviceOrientationAction;
-
- [SerializeField] private bool m_DeselectOnBackgroundClick = true;
- [SerializeField] private UIPointerBehavior m_PointerBehavior = UIPointerBehavior.SingleMouseOrPenButMultiTouchAndTrack;
- [SerializeField, HideInInspector] internal CursorLockBehavior m_CursorLockBehavior = CursorLockBehavior.OutsideScreen;
-
- private static Dictionary<InputAction, InputActionReferenceState> s_InputActionReferenceCounts = new Dictionary<InputAction, InputActionReferenceState>();
-
- private struct InputActionReferenceState
- {
- public int refCount;
- public bool enabledByInputModule;
- }
-
- [NonSerialized] private bool m_ActionsHooked;
- [NonSerialized] private bool m_NeedToPurgeStalePointers;
-
- private Action<InputAction.CallbackContext> m_OnPointDelegate;
- private Action<InputAction.CallbackContext> m_OnMoveDelegate;
- private Action<InputAction.CallbackContext> m_OnLeftClickDelegate;
- private Action<InputAction.CallbackContext> m_OnRightClickDelegate;
- private Action<InputAction.CallbackContext> m_OnMiddleClickDelegate;
- private Action<InputAction.CallbackContext> m_OnScrollWheelDelegate;
- private Action<InputAction.CallbackContext> m_OnTrackedDevicePositionDelegate;
- private Action<InputAction.CallbackContext> m_OnTrackedDeviceOrientationDelegate;
- private Action<object> m_OnControlsChangedDelegate;
-
- // Pointer-type input (also tracking-type).
- [NonSerialized] private int m_CurrentPointerId = -1; // Keeping track of the current pointer avoids searches in most cases.
- [NonSerialized] private int m_CurrentPointerIndex = -1;
- [NonSerialized] internal UIPointerType m_CurrentPointerType = UIPointerType.None;
- 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).
- internal InlinedArray<InputControl> m_PointerTouchControls;
- internal InlinedArray<PointerModel> m_PointerStates;
-
- // Navigation-type input.
- private NavigationModel m_NavigationState;
-
- [NonSerialized] private GameObject m_LocalMultiPlayerRoot;
-
- /// <summary>
- /// Controls the origin point of raycasts when the cursor is locked.
- /// </summary>
- public enum CursorLockBehavior
- {
- /// <summary>
- /// The internal pointer position will be set to -1, -1. This short-circuits the raycasting
- /// logic so no objects will be intersected. This is the default setting.
- /// </summary>
- OutsideScreen,
-
- /// <summary>
- /// Raycasts will originate from the center of the screen. This mode can be useful for
- /// example to check in pointer-driven FPS games if the player is looking at some world-space
- /// object that implements the <see cref="IPointerEnterHandler"/> and <see cref="IPointerExitHandler"/>
- /// interfaces.
- /// </summary>
- ScreenCenter
- }
- }
- }
- #endif
|