DebugLogPopup.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using UnityEngine.EventSystems;
  4. using System.Collections;
  5. using TMPro;
  6. #if UNITY_EDITOR && UNITY_2021_1_OR_NEWER
  7. using Screen = UnityEngine.Device.Screen; // To support Device Simulator on Unity 2021.1+
  8. #endif
  9. // Manager class for the debug popup
  10. namespace IngameDebugConsole
  11. {
  12. public class DebugLogPopup : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
  13. {
  14. private RectTransform popupTransform;
  15. // Dimensions of the popup divided by 2
  16. private Vector2 halfSize;
  17. // Background image that will change color to indicate an alert
  18. private Image backgroundImage;
  19. // Canvas group to modify visibility of the popup
  20. private CanvasGroup canvasGroup;
  21. #pragma warning disable 0649
  22. [SerializeField]
  23. private DebugLogManager debugManager;
  24. [SerializeField]
  25. private TextMeshProUGUI newInfoCountText;
  26. [SerializeField]
  27. private TextMeshProUGUI newWarningCountText;
  28. [SerializeField]
  29. private TextMeshProUGUI newErrorCountText;
  30. [SerializeField]
  31. private Color alertColorInfo;
  32. [SerializeField]
  33. private Color alertColorWarning;
  34. [SerializeField]
  35. private Color alertColorError;
  36. #pragma warning restore 0649
  37. // Number of new debug entries since the log window has been closed
  38. private int newInfoCount = 0, newWarningCount = 0, newErrorCount = 0;
  39. private Color normalColor;
  40. private bool isPopupBeingDragged = false;
  41. private Vector2 normalizedPosition;
  42. // Coroutines for simple code-based animations
  43. private IEnumerator moveToPosCoroutine = null;
  44. public bool IsVisible { get; private set; }
  45. private void Awake()
  46. {
  47. popupTransform = (RectTransform) transform;
  48. backgroundImage = GetComponent<Image>();
  49. canvasGroup = GetComponent<CanvasGroup>();
  50. normalColor = backgroundImage.color;
  51. halfSize = popupTransform.sizeDelta * 0.5f;
  52. Vector2 pos = popupTransform.anchoredPosition;
  53. if( pos.x != 0f || pos.y != 0f )
  54. normalizedPosition = pos.normalized; // Respect the initial popup position set in the prefab
  55. else
  56. normalizedPosition = new Vector2( 0.5f, 0f ); // Right edge by default
  57. }
  58. public void NewLogsArrived( int newInfo, int newWarning, int newError )
  59. {
  60. if( newInfo > 0 )
  61. {
  62. newInfoCount += newInfo;
  63. newInfoCountText.text = newInfoCount.ToString();
  64. }
  65. if( newWarning > 0 )
  66. {
  67. newWarningCount += newWarning;
  68. newWarningCountText.text = newWarningCount.ToString();
  69. }
  70. if( newError > 0 )
  71. {
  72. newErrorCount += newError;
  73. newErrorCountText.text = newErrorCount.ToString();
  74. }
  75. if( newErrorCount > 0 )
  76. backgroundImage.color = alertColorError;
  77. else if( newWarningCount > 0 )
  78. backgroundImage.color = alertColorWarning;
  79. else
  80. backgroundImage.color = alertColorInfo;
  81. }
  82. private void ResetValues()
  83. {
  84. newInfoCount = 0;
  85. newWarningCount = 0;
  86. newErrorCount = 0;
  87. newInfoCountText.text = "0";
  88. newWarningCountText.text = "0";
  89. newErrorCountText.text = "0";
  90. backgroundImage.color = normalColor;
  91. }
  92. // A simple smooth movement animation
  93. private IEnumerator MoveToPosAnimation( Vector2 targetPos )
  94. {
  95. float modifier = 0f;
  96. Vector2 initialPos = popupTransform.anchoredPosition;
  97. while( modifier < 1f )
  98. {
  99. modifier += 4f * Time.unscaledDeltaTime;
  100. popupTransform.anchoredPosition = Vector2.Lerp( initialPos, targetPos, modifier );
  101. yield return null;
  102. }
  103. }
  104. // Popup is clicked
  105. public void OnPointerClick( PointerEventData data )
  106. {
  107. // Hide the popup and show the log window
  108. if( !isPopupBeingDragged )
  109. debugManager.ShowLogWindow();
  110. }
  111. // Hides the log window and shows the popup
  112. public void Show()
  113. {
  114. canvasGroup.blocksRaycasts = true;
  115. canvasGroup.alpha = debugManager.popupOpacity;
  116. IsVisible = true;
  117. // Reset the counters
  118. ResetValues();
  119. // Update position in case resolution was changed while the popup was hidden
  120. UpdatePosition( true );
  121. }
  122. // Hide the popup
  123. public void Hide()
  124. {
  125. canvasGroup.blocksRaycasts = false;
  126. canvasGroup.alpha = 0f;
  127. IsVisible = false;
  128. isPopupBeingDragged = false;
  129. }
  130. public void OnBeginDrag( PointerEventData data )
  131. {
  132. isPopupBeingDragged = true;
  133. // If a smooth movement animation is in progress, cancel it
  134. if( moveToPosCoroutine != null )
  135. {
  136. StopCoroutine( moveToPosCoroutine );
  137. moveToPosCoroutine = null;
  138. }
  139. }
  140. // Reposition the popup
  141. public void OnDrag( PointerEventData data )
  142. {
  143. Vector2 localPoint;
  144. if( RectTransformUtility.ScreenPointToLocalPointInRectangle( debugManager.canvasTR, data.position, data.pressEventCamera, out localPoint ) )
  145. popupTransform.anchoredPosition = localPoint;
  146. }
  147. // Smoothly translate the popup to the nearest edge
  148. public void OnEndDrag( PointerEventData data )
  149. {
  150. isPopupBeingDragged = false;
  151. UpdatePosition( false );
  152. }
  153. // There are 2 different spaces used in these calculations:
  154. // RectTransform space: raw anchoredPosition of the popup that's in range [-canvasSize/2, canvasSize/2]
  155. // Safe area space: Screen.safeArea space that's in range [safeAreaBottomLeft, safeAreaTopRight] where these corner positions
  156. // are all positive (calculated from bottom left corner of the screen instead of the center of the screen)
  157. public void UpdatePosition( bool immediately )
  158. {
  159. Vector2 canvasRawSize = debugManager.canvasTR.rect.size;
  160. // Calculate safe area bounds
  161. float canvasWidth = canvasRawSize.x;
  162. float canvasHeight = canvasRawSize.y;
  163. float canvasBottomLeftX = 0f;
  164. float canvasBottomLeftY = 0f;
  165. if( debugManager.popupAvoidsScreenCutout )
  166. {
  167. #if UNITY_EDITOR || UNITY_ANDROID || UNITY_IOS
  168. Rect safeArea = Screen.safeArea;
  169. int screenWidth = Screen.width;
  170. int screenHeight = Screen.height;
  171. canvasWidth *= safeArea.width / screenWidth;
  172. canvasHeight *= safeArea.height / screenHeight;
  173. canvasBottomLeftX = canvasRawSize.x * ( safeArea.x / screenWidth );
  174. canvasBottomLeftY = canvasRawSize.y * ( safeArea.y / screenHeight );
  175. #endif
  176. }
  177. // Calculate safe area position of the popup
  178. // normalizedPosition allows us to glue the popup to a specific edge of the screen. It becomes useful when
  179. // the popup is at the right edge and we switch from portrait screen orientation to landscape screen orientation.
  180. // Without normalizedPosition, popup could jump to bottom or top edges instead of staying at the right edge
  181. Vector2 pos = canvasRawSize * 0.5f + ( immediately ? new Vector2( normalizedPosition.x * canvasWidth, normalizedPosition.y * canvasHeight ) : ( popupTransform.anchoredPosition - new Vector2( canvasBottomLeftX, canvasBottomLeftY ) ) );
  182. // Find distances to all four edges of the safe area
  183. float distToLeft = pos.x;
  184. float distToRight = canvasWidth - distToLeft;
  185. float distToBottom = pos.y;
  186. float distToTop = canvasHeight - distToBottom;
  187. float horDistance = Mathf.Min( distToLeft, distToRight );
  188. float vertDistance = Mathf.Min( distToBottom, distToTop );
  189. // Find the nearest edge's safe area coordinates
  190. if( horDistance < vertDistance )
  191. {
  192. if( distToLeft < distToRight )
  193. pos = new Vector2( halfSize.x, pos.y );
  194. else
  195. pos = new Vector2( canvasWidth - halfSize.x, pos.y );
  196. pos.y = Mathf.Clamp( pos.y, halfSize.y, canvasHeight - halfSize.y );
  197. }
  198. else
  199. {
  200. if( distToBottom < distToTop )
  201. pos = new Vector2( pos.x, halfSize.y );
  202. else
  203. pos = new Vector2( pos.x, canvasHeight - halfSize.y );
  204. pos.x = Mathf.Clamp( pos.x, halfSize.x, canvasWidth - halfSize.x );
  205. }
  206. pos -= canvasRawSize * 0.5f;
  207. normalizedPosition.Set( pos.x / canvasWidth, pos.y / canvasHeight );
  208. // Safe area's bottom left coordinates are added to pos only after normalizedPosition's value
  209. // is set because normalizedPosition is in range [-canvasWidth / 2, canvasWidth / 2]
  210. pos += new Vector2( canvasBottomLeftX, canvasBottomLeftY );
  211. // If another smooth movement animation is in progress, cancel it
  212. if( moveToPosCoroutine != null )
  213. {
  214. StopCoroutine( moveToPosCoroutine );
  215. moveToPosCoroutine = null;
  216. }
  217. if( immediately )
  218. popupTransform.anchoredPosition = pos;
  219. else
  220. {
  221. // Smoothly translate the popup to the specified position
  222. moveToPosCoroutine = MoveToPosAnimation( pos );
  223. StartCoroutine( moveToPosCoroutine );
  224. }
  225. }
  226. }
  227. }