A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3
4namespace UnityEngine.EventSystems
5{
6 [RequireComponent(typeof(EventSystem))]
7 /// <summary>
8 /// A base module that raises events and sends them to GameObjects.
9 /// </summary>
10 /// <remarks>
11 /// An Input Module is a component of the EventSystem that is responsible for raising events and sending them to GameObjects for handling. The BaseInputModule is a class that all Input Modules in the EventSystem inherit from. Examples of provided modules are TouchInputModule and StandaloneInputModule, if these are inadequate for your project you can create your own by extending from the BaseInputModule.
12 /// </remarks>
13 /// <example>
14 /// <code>
15 /// <![CDATA[
16 /// using UnityEngine;
17 /// using UnityEngine.EventSystems;
18 ///
19 /// /**
20 /// * Create a module that every tick sends a 'Move' event to
21 /// * the target object
22 /// */
23 /// public class MyInputModule : BaseInputModule
24 /// {
25 /// public GameObject m_TargetObject;
26 ///
27 /// public override void Process()
28 /// {
29 /// if (m_TargetObject == null)
30 /// return;
31 /// ExecuteEvents.Execute (m_TargetObject, new BaseEventData (eventSystem), ExecuteEvents.moveHandler);
32 /// }
33 /// }
34 /// ]]>
35 ///</code>
36 /// </example>
37 public abstract class BaseInputModule : UIBehaviour
38 {
39 [NonSerialized]
40 protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>();
41
42 /// <summary>
43 /// True if pointer hover events will be sent to the parent
44 /// </summary>
45 [SerializeField] private bool m_SendPointerHoverToParent = true;
46
47 //This is needed for testing
48 /// <summary>
49 /// True if pointer hover events will be sent to the parent
50 /// </summary>
51 protected internal bool sendPointerHoverToParent { get { return m_SendPointerHoverToParent; } set { m_SendPointerHoverToParent = value; } }
52
53 private AxisEventData m_AxisEventData;
54
55 private EventSystem m_EventSystem;
56 private BaseEventData m_BaseEventData;
57
58 protected BaseInput m_InputOverride;
59 private BaseInput m_DefaultInput;
60
61 /// <summary>
62 /// The current BaseInput being used by the input module.
63 /// </summary>
64 public BaseInput input
65 {
66 get
67 {
68 if (m_InputOverride != null)
69 return m_InputOverride;
70
71 if (m_DefaultInput == null)
72 {
73 var inputs = GetComponents<BaseInput>();
74 foreach (var baseInput in inputs)
75 {
76 // We dont want to use any classes that derrive from BaseInput for default.
77 if (baseInput != null && baseInput.GetType() == typeof(BaseInput))
78 {
79 m_DefaultInput = baseInput;
80 break;
81 }
82 }
83
84 if (m_DefaultInput == null)
85 m_DefaultInput = gameObject.AddComponent<BaseInput>();
86 }
87
88 return m_DefaultInput;
89 }
90 }
91
92 /// <summary>
93 /// Used to override the default BaseInput for the input module.
94 /// </summary>
95 /// <remarks>
96 /// With this it is possible to bypass the Input system with your own but still use the same InputModule. For example this can be used to feed fake input into the UI or interface with a different input system.
97 /// </remarks>
98 public BaseInput inputOverride
99 {
100 get { return m_InputOverride; }
101 set { m_InputOverride = value; }
102 }
103
104 protected EventSystem eventSystem
105 {
106 get { return m_EventSystem; }
107 }
108
109 protected override void OnEnable()
110 {
111 base.OnEnable();
112 m_EventSystem = GetComponent<EventSystem>();
113 m_EventSystem.UpdateModules();
114 }
115
116 protected override void OnDisable()
117 {
118 m_EventSystem.UpdateModules();
119 base.OnDisable();
120 }
121
122 /// <summary>
123 /// Process the current tick for the module.
124 /// </summary>
125 public abstract void Process();
126
127 /// <summary>
128 /// Return the first valid RaycastResult.
129 /// </summary>
130 protected static RaycastResult FindFirstRaycast(List<RaycastResult> candidates)
131 {
132 var candidatesCount = candidates.Count;
133 for (var i = 0; i < candidatesCount; ++i)
134 {
135 if (candidates[i].gameObject == null)
136 continue;
137
138 return candidates[i];
139 }
140 return new RaycastResult();
141 }
142
143 /// <summary>
144 /// Given an input movement, determine the best MoveDirection.
145 /// </summary>
146 /// <param name="x">X movement.</param>
147 /// <param name="y">Y movement.</param>
148 protected static MoveDirection DetermineMoveDirection(float x, float y)
149 {
150 return DetermineMoveDirection(x, y, 0.6f);
151 }
152
153 /// <summary>
154 /// Given an input movement, determine the best MoveDirection.
155 /// </summary>
156 /// <param name="x">X movement.</param>
157 /// <param name="y">Y movement.</param>
158 /// <param name="deadZone">Dead zone.</param>
159 protected static MoveDirection DetermineMoveDirection(float x, float y, float deadZone)
160 {
161 // if vector is too small... just return
162 if (new Vector2(x, y).sqrMagnitude < deadZone * deadZone)
163 return MoveDirection.None;
164
165 if (Mathf.Abs(x) > Mathf.Abs(y))
166 {
167 return x > 0 ? MoveDirection.Right : MoveDirection.Left;
168 }
169
170 return y > 0 ? MoveDirection.Up : MoveDirection.Down;
171 }
172
173 /// <summary>
174 /// Given 2 GameObjects, return a common root GameObject (or null).
175 /// </summary>
176 /// <param name="g1">GameObject to compare</param>
177 /// <param name="g2">GameObject to compare</param>
178 /// <returns></returns>
179 protected static GameObject FindCommonRoot(GameObject g1, GameObject g2)
180 {
181 if (g1 == null || g2 == null)
182 return null;
183
184 var t1 = g1.transform;
185 while (t1 != null)
186 {
187 var t2 = g2.transform;
188 while (t2 != null)
189 {
190 if (t1 == t2)
191 return t1.gameObject;
192 t2 = t2.parent;
193 }
194 t1 = t1.parent;
195 }
196 return null;
197 }
198
199 // walk up the tree till a common root between the last entered and the current entered is found
200 // send exit events up to (but not including) the common root. Then send enter events up to
201 // (but not including) the common root.
202 // Send move events before exit, after enter, and on hovered objects when pointer data has changed.
203 protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget)
204 {
205 // if we have no target / pointerEnter has been deleted
206 // just send exit events to anything we are tracking
207 // then exit
208 if (newEnterTarget == null || currentPointerData.pointerEnter == null)
209 {
210 var hoveredCount = currentPointerData.hovered.Count;
211 for (var i = 0; i < hoveredCount; ++i)
212 {
213 currentPointerData.fullyExited = true;
214 ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler);
215 ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler);
216 }
217
218 currentPointerData.hovered.Clear();
219
220 if (newEnterTarget == null)
221 {
222 currentPointerData.pointerEnter = null;
223 return;
224 }
225 }
226
227 // if we have not changed hover target
228 if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget)
229 {
230 if (currentPointerData.IsPointerMoving())
231 {
232 var hoveredCount = currentPointerData.hovered.Count;
233 for (var i = 0; i < hoveredCount; ++i)
234 ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler);
235 }
236 return;
237 }
238
239 GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget);
240 GameObject pointerParent = ((Component)newEnterTarget.GetComponentInParent<IPointerExitHandler>())?.gameObject;
241
242 // and we already an entered object from last time
243 if (currentPointerData.pointerEnter != null)
244 {
245 // send exit handler call to all elements in the chain
246 // until we reach the new target, or null!
247 // ** or when !m_SendPointerEnterToParent, stop when meeting a gameobject with an exit event handler
248 Transform t = currentPointerData.pointerEnter.transform;
249
250 while (t != null)
251 {
252 // if we reach the common root break out!
253 if (m_SendPointerHoverToParent && commonRoot != null && commonRoot.transform == t)
254 break;
255
256 // if we reach a PointerExitEvent break out!
257 if (!m_SendPointerHoverToParent && pointerParent == t.gameObject)
258 break;
259
260 currentPointerData.fullyExited = t.gameObject != commonRoot && currentPointerData.pointerEnter != newEnterTarget;
261 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler);
262 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler);
263 currentPointerData.hovered.Remove(t.gameObject);
264
265 if (m_SendPointerHoverToParent) t = t.parent;
266
267 // if we reach the common root break out!
268 if (commonRoot != null && commonRoot.transform == t)
269 break;
270
271 if (!m_SendPointerHoverToParent) t = t.parent;
272 }
273 }
274
275 // now issue the enter call up to but not including the common root
276 var oldPointerEnter = currentPointerData.pointerEnter;
277 currentPointerData.pointerEnter = newEnterTarget;
278 if (newEnterTarget != null)
279 {
280 Transform t = newEnterTarget.transform;
281
282 while (t != null)
283 {
284 currentPointerData.reentered = t.gameObject == commonRoot && t.gameObject != oldPointerEnter;
285 // if we are sending the event to parent, they are already in hover mode at that point. No need to bubble up the event.
286 if (m_SendPointerHoverToParent && currentPointerData.reentered)
287 break;
288
289 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler);
290 ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler);
291 currentPointerData.hovered.Add(t.gameObject);
292
293 // stop when encountering an object with the pointerEnterHandler
294 if (!m_SendPointerHoverToParent && t.gameObject.GetComponent<IPointerEnterHandler>() != null)
295 break;
296
297 if (m_SendPointerHoverToParent) t = t.parent;
298
299 // if we reach the common root break out!
300 if (commonRoot != null && commonRoot.transform == t)
301 break;
302
303 if (!m_SendPointerHoverToParent) t = t.parent;
304 }
305 }
306 }
307
308 /// <summary>
309 /// Given some input data generate an AxisEventData that can be used by the event system.
310 /// </summary>
311 /// <param name="x">X movement.</param>
312 /// <param name="y">Y movement.</param>
313 /// <param name="deadZone">Dead zone.</param>
314 protected virtual AxisEventData GetAxisEventData(float x, float y, float moveDeadZone)
315 {
316 if (m_AxisEventData == null)
317 m_AxisEventData = new AxisEventData(eventSystem);
318
319 m_AxisEventData.Reset();
320 m_AxisEventData.moveVector = new Vector2(x, y);
321 m_AxisEventData.moveDir = DetermineMoveDirection(x, y, moveDeadZone);
322 return m_AxisEventData;
323 }
324
325 /// <summary>
326 /// Generate a BaseEventData that can be used by the EventSystem.
327 /// </summary>
328 protected virtual BaseEventData GetBaseEventData()
329 {
330 if (m_BaseEventData == null)
331 m_BaseEventData = new BaseEventData(eventSystem);
332
333 m_BaseEventData.Reset();
334 return m_BaseEventData;
335 }
336
337 /// <summary>
338 /// If the module is pointer based, then override this to return true if the pointer is over an event system object.
339 /// </summary>
340 /// <param name="pointerId">Pointer ID</param>
341 /// <returns>Is the given pointer over an event system object?</returns>
342 public virtual bool IsPointerOverGameObject(int pointerId)
343 {
344 return false;
345 }
346
347 /// <summary>
348 /// Should the module be activated.
349 /// </summary>
350 public virtual bool ShouldActivateModule()
351 {
352 return enabled && gameObject.activeInHierarchy;
353 }
354
355 /// <summary>
356 /// Called when the module is deactivated. Override this if you want custom code to execute when you deactivate your module.
357 /// </summary>
358 public virtual void DeactivateModule()
359 {}
360
361 /// <summary>
362 /// Called when the module is activated. Override this if you want custom code to execute when you activate your module.
363 /// </summary>
364 public virtual void ActivateModule()
365 {}
366
367 /// <summary>
368 /// Update the internal state of the Module.
369 /// </summary>
370 public virtual void UpdateModule()
371 {}
372
373 /// <summary>
374 /// Check to see if the module is supported. Override this if you have a platform specific module (eg. TouchInputModule that you do not want to activate on standalone.)
375 /// </summary>
376 /// <returns>Is the module supported.</returns>
377 public virtual bool IsModuleSupported()
378 {
379 return true;
380 }
381
382 /// <summary>
383 /// Returns Id of the pointer following <see cref="UnityEngine.UIElements.PointerId"/> convention.
384 /// </summary>
385 /// <param name="sourcePointerData">PointerEventData whose pointerId will be converted to UI Toolkit pointer convention.</param>
386 /// <seealso cref="UnityEngine.UIElements.IPointerEvent" />
387 public virtual int ConvertUIToolkitPointerId(PointerEventData sourcePointerData)
388 {
389#if PACKAGE_UITOOLKIT
390 return sourcePointerData.pointerId < 0 ?
391 UIElements.PointerId.mousePointerId :
392 UIElements.PointerId.touchPointerIdBase + sourcePointerData.pointerId;
393#else
394 return -1;
395#endif
396 }
397
398 /// <summary>
399 /// Converts PointerEventData.scrollDelta to corresponding number of ticks of the scroll wheel.
400 /// </summary>
401 /// <remarks>
402 /// Input Module implementations are free to apply a scaling factor to their PointerEventData's scrollDelta.
403 /// This method can be used when a system needs to treat scaling-independent input values.
404 /// </remarks>
405 public virtual Vector2 ConvertPointerEventScrollDeltaToTicks(Vector2 scrollDelta)
406 {
407 return scrollDelta / input.mouseScrollDeltaPerTick;
408 }
409 }
410}