A game about forced loneliness, made by TACStudios
1using System;
2using UnityEngine;
3using UnityEngine.Serialization;
4
5namespace UnityEngine.EventSystems
6{
7 [AddComponentMenu("Event/Standalone Input Module")]
8 /// <summary>
9 /// A BaseInputModule designed for mouse / keyboard / controller input.
10 /// </summary>
11 /// <remarks>
12 /// Input module for working with, mouse, keyboard, or controller.
13 /// </remarks>
14 public class StandaloneInputModule : PointerInputModule
15 {
16 private float m_PrevActionTime;
17 private Vector2 m_LastMoveVector;
18 private int m_ConsecutiveMoveCount = 0;
19
20 private Vector2 m_LastMousePosition;
21 private Vector2 m_MousePosition;
22
23 private GameObject m_CurrentFocusedGameObject;
24
25 private PointerEventData m_InputPointerEvent;
26
27 private const float doubleClickTime = 0.3f;
28
29 protected StandaloneInputModule()
30 {
31 }
32
33 [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)]
34 public enum InputMode
35 {
36 Mouse,
37 Buttons
38 }
39
40 [Obsolete("Mode is no longer needed on input module as it handles both mouse and keyboard simultaneously.", false)]
41 public InputMode inputMode
42 {
43 get { return InputMode.Mouse; }
44 }
45
46 [SerializeField]
47 private string m_HorizontalAxis = "Horizontal";
48
49 /// <summary>
50 /// Name of the vertical axis for movement (if axis events are used).
51 /// </summary>
52 [SerializeField]
53 private string m_VerticalAxis = "Vertical";
54
55 /// <summary>
56 /// Name of the submit button.
57 /// </summary>
58 [SerializeField]
59 private string m_SubmitButton = "Submit";
60
61 /// <summary>
62 /// Name of the submit button.
63 /// </summary>
64 [SerializeField]
65 private string m_CancelButton = "Cancel";
66
67 [SerializeField]
68 private float m_InputActionsPerSecond = 10;
69
70 [SerializeField]
71 private float m_RepeatDelay = 0.5f;
72
73 [SerializeField]
74 [FormerlySerializedAs("m_AllowActivationOnMobileDevice")]
75 [HideInInspector]
76 private bool m_ForceModuleActive;
77
78 [Obsolete("allowActivationOnMobileDevice has been deprecated. Use forceModuleActive instead (UnityUpgradable) -> forceModuleActive")]
79 public bool allowActivationOnMobileDevice
80 {
81 get { return m_ForceModuleActive; }
82 set { m_ForceModuleActive = value; }
83 }
84
85 /// <summary>
86 /// Force this module to be active.
87 /// </summary>
88 /// <remarks>
89 /// If there is no module active with higher priority (ordered in the inspector) this module will be forced active even if valid enabling conditions are not met.
90 /// </remarks>
91
92 [Obsolete("forceModuleActive has been deprecated. There is no need to force the module awake as StandaloneInputModule works for all platforms")]
93 public bool forceModuleActive
94 {
95 get { return m_ForceModuleActive; }
96 set { m_ForceModuleActive = value; }
97 }
98
99 /// <summary>
100 /// Number of keyboard / controller inputs allowed per second.
101 /// </summary>
102 public float inputActionsPerSecond
103 {
104 get { return m_InputActionsPerSecond; }
105 set { m_InputActionsPerSecond = value; }
106 }
107
108 /// <summary>
109 /// Delay in seconds before the input actions per second repeat rate takes effect.
110 /// </summary>
111 /// <remarks>
112 /// If the same direction is sustained, the inputActionsPerSecond property can be used to control the rate at which events are fired. However, it can be desirable that the first repetition is delayed, so the user doesn't get repeated actions by accident.
113 /// </remarks>
114 public float repeatDelay
115 {
116 get { return m_RepeatDelay; }
117 set { m_RepeatDelay = value; }
118 }
119
120 /// <summary>
121 /// Name of the horizontal axis for movement (if axis events are used).
122 /// </summary>
123 public string horizontalAxis
124 {
125 get { return m_HorizontalAxis; }
126 set { m_HorizontalAxis = value; }
127 }
128
129 /// <summary>
130 /// Name of the vertical axis for movement (if axis events are used).
131 /// </summary>
132 public string verticalAxis
133 {
134 get { return m_VerticalAxis; }
135 set { m_VerticalAxis = value; }
136 }
137
138 /// <summary>
139 /// Maximum number of input events handled per second.
140 /// </summary>
141 public string submitButton
142 {
143 get { return m_SubmitButton; }
144 set { m_SubmitButton = value; }
145 }
146
147 /// <summary>
148 /// Input manager name for the 'cancel' button.
149 /// </summary>
150 public string cancelButton
151 {
152 get { return m_CancelButton; }
153 set { m_CancelButton = value; }
154 }
155
156 private bool ShouldIgnoreEventsOnNoFocus()
157 {
158#if UNITY_EDITOR
159 return !UnityEditor.EditorApplication.isRemoteConnected;
160#else
161 return true;
162#endif
163 }
164
165 public override void UpdateModule()
166 {
167 if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
168 {
169 if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
170 {
171 ReleaseMouse(m_InputPointerEvent, m_InputPointerEvent.pointerCurrentRaycast.gameObject);
172 }
173
174 m_InputPointerEvent = null;
175
176 return;
177 }
178
179 m_LastMousePosition = m_MousePosition;
180 m_MousePosition = input.mousePosition;
181 }
182
183 private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo)
184 {
185 ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
186
187 var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
188
189 // PointerClick and Drop events
190 if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
191 {
192 ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
193 }
194 if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
195 {
196 ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
197 }
198
199 pointerEvent.eligibleForClick = false;
200 pointerEvent.pointerPress = null;
201 pointerEvent.rawPointerPress = null;
202 pointerEvent.pointerClick = null;
203
204 if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
205 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
206
207 pointerEvent.dragging = false;
208 pointerEvent.pointerDrag = null;
209
210 // redo pointer enter / exit to refresh state
211 // so that if we moused over something that ignored it before
212 // due to having pressed on something else
213 // it now gets it.
214 if (currentOverGo != pointerEvent.pointerEnter)
215 {
216 HandlePointerExitAndEnter(pointerEvent, null);
217 HandlePointerExitAndEnter(pointerEvent, currentOverGo);
218 }
219
220 m_InputPointerEvent = pointerEvent;
221 }
222
223 public override bool ShouldActivateModule()
224 {
225 if (!base.ShouldActivateModule())
226 return false;
227
228 var shouldActivate = m_ForceModuleActive;
229 shouldActivate |= input.GetButtonDown(m_SubmitButton);
230 shouldActivate |= input.GetButtonDown(m_CancelButton);
231 shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_HorizontalAxis), 0.0f);
232 shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_VerticalAxis), 0.0f);
233 shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f;
234 shouldActivate |= input.GetMouseButtonDown(0);
235
236 if (input.touchCount > 0)
237 shouldActivate = true;
238
239 return shouldActivate;
240 }
241
242 /// <summary>
243 /// See BaseInputModule.
244 /// </summary>
245 public override void ActivateModule()
246 {
247 if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
248 return;
249
250 base.ActivateModule();
251 m_MousePosition = input.mousePosition;
252 m_LastMousePosition = input.mousePosition;
253
254 var toSelect = eventSystem.currentSelectedGameObject;
255 if (toSelect == null)
256 toSelect = eventSystem.firstSelectedGameObject;
257
258 eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
259 }
260
261 /// <summary>
262 /// See BaseInputModule.
263 /// </summary>
264 public override void DeactivateModule()
265 {
266 base.DeactivateModule();
267 ClearSelection();
268 }
269
270 public override void Process()
271 {
272 if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
273 return;
274
275 bool usedEvent = SendUpdateEventToSelectedObject();
276
277 // case 1004066 - touch / mouse events should be processed before navigation events in case
278 // they change the current selected gameobject and the submit button is a touch / mouse button.
279
280 // touch needs to take precedence because of the mouse emulation layer
281 if (!ProcessTouchEvents() && input.mousePresent)
282 ProcessMouseEvent();
283
284 if (eventSystem.sendNavigationEvents)
285 {
286 if (!usedEvent)
287 usedEvent |= SendMoveEventToSelectedObject();
288
289 if (!usedEvent)
290 SendSubmitEventToSelectedObject();
291 }
292 }
293
294 private bool ProcessTouchEvents()
295 {
296 for (int i = 0; i < input.touchCount; ++i)
297 {
298 Touch touch = input.GetTouch(i);
299
300 if (touch.type == TouchType.Indirect)
301 continue;
302
303 bool released;
304 bool pressed;
305 var pointer = GetTouchPointerEventData(touch, out pressed, out released);
306
307 ProcessTouchPress(pointer, pressed, released);
308
309 if (!released)
310 {
311 ProcessMove(pointer);
312 ProcessDrag(pointer);
313 }
314 else
315 RemovePointerData(pointer);
316 }
317 return input.touchCount > 0;
318 }
319
320 /// <summary>
321 /// This method is called by Unity whenever a touch event is processed. Override this method with a custom implementation to process touch events yourself.
322 /// </summary>
323 /// <param name="pointerEvent">Event data relating to the touch event, such as position and ID to be passed to the touch event destination object.</param>
324 /// <param name="pressed">This is true for the first frame of a touch event, and false thereafter. This can therefore be used to determine the instant a touch event occurred.</param>
325 /// <param name="released">This is true only for the last frame of a touch event.</param>
326 /// <remarks>
327 /// This method can be overridden in derived classes to change how touch press events are handled.
328 /// </remarks>
329 protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
330 {
331 var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
332
333 // PointerDown notification
334 if (pressed)
335 {
336 pointerEvent.eligibleForClick = true;
337 pointerEvent.delta = Vector2.zero;
338 pointerEvent.dragging = false;
339 pointerEvent.useDragThreshold = true;
340 pointerEvent.pressPosition = pointerEvent.position;
341 pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
342
343 DeselectIfSelectionChanged(currentOverGo, pointerEvent);
344
345 if (pointerEvent.pointerEnter != currentOverGo)
346 {
347 // send a pointer enter to the touched element if it isn't the one to select...
348 HandlePointerExitAndEnter(pointerEvent, currentOverGo);
349 pointerEvent.pointerEnter = currentOverGo;
350 }
351
352 var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
353 if (resetDiffTime >= doubleClickTime)
354 {
355 pointerEvent.clickCount = 0;
356 }
357
358 // search for the control that will receive the press
359 // if we can't find a press handler set the press
360 // handler to be what would receive a click.
361 var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
362
363 var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
364
365 // didnt find a press handler... search for a click handler
366 if (newPressed == null)
367 newPressed = newClick;
368
369 // Debug.Log("Pressed: " + newPressed);
370
371 float time = Time.unscaledTime;
372
373 if (newPressed == pointerEvent.lastPress)
374 {
375 var diffTime = time - pointerEvent.clickTime;
376 if (diffTime < doubleClickTime)
377 ++pointerEvent.clickCount;
378 else
379 pointerEvent.clickCount = 1;
380
381 pointerEvent.clickTime = time;
382 }
383 else
384 {
385 pointerEvent.clickCount = 1;
386 }
387
388 pointerEvent.pointerPress = newPressed;
389 pointerEvent.rawPointerPress = currentOverGo;
390 pointerEvent.pointerClick = newClick;
391
392 pointerEvent.clickTime = time;
393
394 // Save the drag handler as well
395 pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
396
397 if (pointerEvent.pointerDrag != null)
398 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
399 }
400
401 // PointerUp notification
402 if (released)
403 {
404 // Debug.Log("Executing pressup on: " + pointer.pointerPress);
405 ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
406
407 // Debug.Log("KeyCode: " + pointer.eventData.keyCode);
408
409 // see if we mouse up on the same element that we clicked on...
410 var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
411
412 // PointerClick and Drop events
413 if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
414 {
415 ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
416 }
417
418 if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
419 {
420 ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
421 }
422
423 pointerEvent.eligibleForClick = false;
424 pointerEvent.pointerPress = null;
425 pointerEvent.rawPointerPress = null;
426 pointerEvent.pointerClick = null;
427
428 if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
429 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
430
431 pointerEvent.dragging = false;
432 pointerEvent.pointerDrag = null;
433
434 // send exit events as we need to simulate this on touch up on touch device
435 ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
436 pointerEvent.pointerEnter = null;
437 }
438
439 m_InputPointerEvent = pointerEvent;
440 }
441
442 /// <summary>
443 /// Calculate and send a submit event to the current selected object.
444 /// </summary>
445 /// <returns>If the submit event was used by the selected object.</returns>
446 protected bool SendSubmitEventToSelectedObject()
447 {
448 if (eventSystem.currentSelectedGameObject == null)
449 return false;
450
451 var data = GetBaseEventData();
452 if (input.GetButtonDown(m_SubmitButton))
453 ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.submitHandler);
454
455 if (input.GetButtonDown(m_CancelButton))
456 ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.cancelHandler);
457 return data.used;
458 }
459
460 private Vector2 GetRawMoveVector()
461 {
462 Vector2 move = Vector2.zero;
463 move.x = input.GetAxisRaw(m_HorizontalAxis);
464 move.y = input.GetAxisRaw(m_VerticalAxis);
465
466 if (input.GetButtonDown(m_HorizontalAxis))
467 {
468 if (move.x < 0)
469 move.x = -1f;
470 if (move.x > 0)
471 move.x = 1f;
472 }
473 if (input.GetButtonDown(m_VerticalAxis))
474 {
475 if (move.y < 0)
476 move.y = -1f;
477 if (move.y > 0)
478 move.y = 1f;
479 }
480 return move;
481 }
482
483 /// <summary>
484 /// Calculate and send a move event to the current selected object.
485 /// </summary>
486 /// <returns>If the move event was used by the selected object.</returns>
487 protected bool SendMoveEventToSelectedObject()
488 {
489 float time = Time.unscaledTime;
490
491 Vector2 movement = GetRawMoveVector();
492 if (Mathf.Approximately(movement.x, 0f) && Mathf.Approximately(movement.y, 0f))
493 {
494 m_ConsecutiveMoveCount = 0;
495 return false;
496 }
497
498 bool similarDir = (Vector2.Dot(movement, m_LastMoveVector) > 0);
499
500 // If direction didn't change at least 90 degrees, wait for delay before allowing consequtive event.
501 if (similarDir && m_ConsecutiveMoveCount == 1)
502 {
503 if (time <= m_PrevActionTime + m_RepeatDelay)
504 return false;
505 }
506 // If direction changed at least 90 degree, or we already had the delay, repeat at repeat rate.
507 else
508 {
509 if (time <= m_PrevActionTime + 1f / m_InputActionsPerSecond)
510 return false;
511 }
512
513 var axisEventData = GetAxisEventData(movement.x, movement.y, 0.6f);
514
515 if (axisEventData.moveDir != MoveDirection.None)
516 {
517 ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, axisEventData, ExecuteEvents.moveHandler);
518 if (!similarDir)
519 m_ConsecutiveMoveCount = 0;
520 m_ConsecutiveMoveCount++;
521 m_PrevActionTime = time;
522 m_LastMoveVector = movement;
523 }
524 else
525 {
526 m_ConsecutiveMoveCount = 0;
527 }
528
529 return axisEventData.used;
530 }
531
532 protected void ProcessMouseEvent()
533 {
534 ProcessMouseEvent(0);
535 }
536
537 [Obsolete("This method is no longer checked, overriding it with return true does nothing!")]
538 protected virtual bool ForceAutoSelect()
539 {
540 return false;
541 }
542
543 /// <summary>
544 /// Process all mouse events.
545 /// </summary>
546 protected void ProcessMouseEvent(int id)
547 {
548 var mouseData = GetMousePointerEventData(id);
549 var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;
550
551 m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
552
553 // Process the first mouse button fully
554 ProcessMousePress(leftButtonData);
555 ProcessMove(leftButtonData.buttonData);
556 ProcessDrag(leftButtonData.buttonData);
557
558 // Now process right / middle clicks
559 ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData);
560 ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData);
561 ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData);
562 ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData);
563
564 if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
565 {
566 var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject);
567 ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler);
568 }
569 }
570
571 protected bool SendUpdateEventToSelectedObject()
572 {
573 if (eventSystem.currentSelectedGameObject == null)
574 return false;
575
576 var data = GetBaseEventData();
577 ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);
578 return data.used;
579 }
580
581 /// <summary>
582 /// Calculate and process any mouse button state changes.
583 /// </summary>
584 protected void ProcessMousePress(MouseButtonEventData data)
585 {
586 var pointerEvent = data.buttonData;
587 var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
588
589 // PointerDown notification
590 if (data.PressedThisFrame())
591 {
592 pointerEvent.eligibleForClick = true;
593 pointerEvent.delta = Vector2.zero;
594 pointerEvent.dragging = false;
595 pointerEvent.useDragThreshold = true;
596 pointerEvent.pressPosition = pointerEvent.position;
597 pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
598
599 DeselectIfSelectionChanged(currentOverGo, pointerEvent);
600
601 var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
602 if (resetDiffTime >= doubleClickTime)
603 {
604 pointerEvent.clickCount = 0;
605 }
606
607 // search for the control that will receive the press
608 // if we can't find a press handler set the press
609 // handler to be what would receive a click.
610 var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
611 var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
612
613 // didnt find a press handler... search for a click handler
614 if (newPressed == null)
615 newPressed = newClick;
616
617 // Debug.Log("Pressed: " + newPressed);
618
619 float time = Time.unscaledTime;
620
621 if (newPressed == pointerEvent.lastPress)
622 {
623 var diffTime = time - pointerEvent.clickTime;
624 if (diffTime < doubleClickTime)
625 ++pointerEvent.clickCount;
626 else
627 pointerEvent.clickCount = 1;
628
629 pointerEvent.clickTime = time;
630 }
631 else
632 {
633 pointerEvent.clickCount = 1;
634 }
635
636 pointerEvent.pointerPress = newPressed;
637 pointerEvent.rawPointerPress = currentOverGo;
638 pointerEvent.pointerClick = newClick;
639
640 pointerEvent.clickTime = time;
641
642 // Save the drag handler as well
643 pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
644
645 if (pointerEvent.pointerDrag != null)
646 ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
647
648 m_InputPointerEvent = pointerEvent;
649 }
650
651 // PointerUp notification
652 if (data.ReleasedThisFrame())
653 {
654 ReleaseMouse(pointerEvent, currentOverGo);
655 }
656 }
657
658 protected GameObject GetCurrentFocusedGameObject()
659 {
660 return m_CurrentFocusedGameObject;
661 }
662 }
663}