DebugLogItem.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. using UnityEngine;
  2. using UnityEngine.UI;
  3. using UnityEngine.EventSystems;
  4. using System.Text;
  5. using TMPro;
  6. #if UNITY_EDITOR
  7. using UnityEditor;
  8. using System.Text.RegularExpressions;
  9. #endif
  10. // A UI element to show information about a debug entry
  11. namespace IngameDebugConsole
  12. {
  13. public class DebugLogItem : MonoBehaviour, IPointerClickHandler
  14. {
  15. #pragma warning disable 0649
  16. // Cached components
  17. [SerializeField]
  18. private RectTransform transformComponent;
  19. public RectTransform Transform { get { return transformComponent; } }
  20. [SerializeField]
  21. private Image imageComponent;
  22. public Image Image { get { return imageComponent; } }
  23. [SerializeField]
  24. private CanvasGroup canvasGroupComponent;
  25. public CanvasGroup CanvasGroup { get { return canvasGroupComponent; } }
  26. [SerializeField]
  27. private TextMeshProUGUI logText;
  28. [SerializeField]
  29. private Image logTypeImage;
  30. // Objects related to the collapsed count of the debug entry
  31. [SerializeField]
  32. private GameObject logCountParent;
  33. [SerializeField]
  34. private TextMeshProUGUI logCountText;
  35. [SerializeField]
  36. private Button copyLogButton;
  37. #pragma warning restore 0649
  38. // Debug entry to show with this log item
  39. private DebugLogEntry logEntry;
  40. public DebugLogEntry Entry { get { return logEntry; } }
  41. private DebugLogEntryTimestamp? logEntryTimestamp;
  42. public DebugLogEntryTimestamp? Timestamp { get { return logEntryTimestamp; } }
  43. // Index of the entry in the list of entries
  44. [System.NonSerialized] public int Index;
  45. private bool isExpanded;
  46. public bool Expanded { get { return isExpanded; } }
  47. private Vector2 logTextOriginalPosition;
  48. private Vector2 logTextOriginalSize;
  49. private float copyLogButtonHeight;
  50. private DebugLogRecycledListView listView;
  51. public void Initialize( DebugLogRecycledListView listView )
  52. {
  53. this.listView = listView;
  54. logTextOriginalPosition = logText.rectTransform.anchoredPosition;
  55. logTextOriginalSize = logText.rectTransform.sizeDelta;
  56. copyLogButtonHeight = ( copyLogButton.transform as RectTransform ).anchoredPosition.y + ( copyLogButton.transform as RectTransform ).sizeDelta.y + 2f; // 2f: space between text and button
  57. if (listView.manager.logItemFontOverride != null)
  58. logText.font = listView.manager.logItemFontOverride;
  59. copyLogButton.onClick.AddListener( CopyLog );
  60. #if !UNITY_EDITOR && UNITY_WEBGL
  61. copyLogButton.gameObject.AddComponent<DebugLogItemCopyWebGL>().Initialize( this );
  62. #endif
  63. }
  64. public void SetContent( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp, int entryIndex, bool isExpanded )
  65. {
  66. this.logEntry = logEntry;
  67. this.logEntryTimestamp = logEntryTimestamp;
  68. this.Index = entryIndex;
  69. this.isExpanded = isExpanded;
  70. Vector2 size = transformComponent.sizeDelta;
  71. if( isExpanded )
  72. {
  73. size.y = listView.SelectedItemHeight;
  74. if( !copyLogButton.gameObject.activeSelf )
  75. {
  76. copyLogButton.gameObject.SetActive( true );
  77. logText.rectTransform.anchoredPosition = new Vector2( logTextOriginalPosition.x, logTextOriginalPosition.y + copyLogButtonHeight * 0.5f );
  78. logText.rectTransform.sizeDelta = logTextOriginalSize - new Vector2( 0f, copyLogButtonHeight );
  79. }
  80. }
  81. else
  82. {
  83. size.y = listView.ItemHeight;
  84. if( copyLogButton.gameObject.activeSelf )
  85. {
  86. copyLogButton.gameObject.SetActive( false );
  87. logText.rectTransform.anchoredPosition = logTextOriginalPosition;
  88. logText.rectTransform.sizeDelta = logTextOriginalSize;
  89. }
  90. }
  91. transformComponent.sizeDelta = size;
  92. SetText( logEntry, logEntryTimestamp, isExpanded );
  93. logTypeImage.sprite = DebugLogManager.logSpriteRepresentations[(int) logEntry.logType];
  94. }
  95. // Show the collapsed count of the debug entry
  96. public void ShowCount()
  97. {
  98. logCountText.SetText( "{0}", logEntry.count );
  99. if( !logCountParent.activeSelf )
  100. logCountParent.SetActive( true );
  101. }
  102. // Hide the collapsed count of the debug entry
  103. public void HideCount()
  104. {
  105. if( logCountParent.activeSelf )
  106. logCountParent.SetActive( false );
  107. }
  108. // Update the debug entry's displayed timestamp
  109. public void UpdateTimestamp( DebugLogEntryTimestamp timestamp )
  110. {
  111. logEntryTimestamp = timestamp;
  112. if( isExpanded || listView.manager.alwaysDisplayTimestamps )
  113. SetText( logEntry, timestamp, isExpanded );
  114. }
  115. private void SetText(DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp, bool isExpanded)
  116. {
  117. string text = isExpanded ? logEntry.ToString() : logEntry.logString;
  118. int maxLogLength = isExpanded ? listView.manager.maxExpandedLogLength : listView.manager.maxCollapsedLogLength;
  119. if (!logEntryTimestamp.HasValue || (!isExpanded && !listView.manager.alwaysDisplayTimestamps))
  120. {
  121. if (text.Length <= maxLogLength)
  122. logText.text = text;
  123. else
  124. {
  125. if (listView.manager.textBuffer.Length < maxLogLength)
  126. listView.manager.textBuffer = new char[maxLogLength];
  127. text.CopyTo(0, listView.manager.textBuffer, 0, maxLogLength);
  128. logText.SetText(listView.manager.textBuffer, 0, maxLogLength);
  129. }
  130. }
  131. else
  132. {
  133. StringBuilder sb = listView.manager.sharedStringBuilder;
  134. sb.Length = 0;
  135. if (isExpanded)
  136. {
  137. logEntryTimestamp.Value.AppendFullTimestamp(sb);
  138. sb.Append(": ").Append(text, 0, Mathf.Min(text.Length, maxLogLength - sb.Length));
  139. }
  140. else
  141. {
  142. logEntryTimestamp.Value.AppendTime(sb);
  143. sb.Append(" ").Append(text, 0, Mathf.Min(text.Length, maxLogLength - sb.Length));
  144. }
  145. if (listView.manager.textBuffer.Length < sb.Length)
  146. listView.manager.textBuffer = new char[sb.Length];
  147. sb.CopyTo(0, listView.manager.textBuffer, 0, sb.Length);
  148. logText.SetText(listView.manager.textBuffer, 0, sb.Length);
  149. }
  150. }
  151. // This log item is clicked, show the debug entry's stack trace
  152. public void OnPointerClick( PointerEventData eventData )
  153. {
  154. #if UNITY_EDITOR
  155. if( eventData.button == PointerEventData.InputButton.Right )
  156. {
  157. Match regex = Regex.Match( logEntry.stackTrace, @"\(at .*\.cs:[0-9]+\)$", RegexOptions.Multiline );
  158. if( regex.Success )
  159. {
  160. string line = logEntry.stackTrace.Substring( regex.Index + 4, regex.Length - 5 );
  161. int lineSeparator = line.IndexOf( ':' );
  162. MonoScript script = AssetDatabase.LoadAssetAtPath<MonoScript>( line.Substring( 0, lineSeparator ) );
  163. if( script != null )
  164. AssetDatabase.OpenAsset( script, int.Parse( line.Substring( lineSeparator + 1 ) ) );
  165. }
  166. }
  167. else
  168. listView.OnLogItemClicked( this );
  169. #else
  170. listView.OnLogItemClicked( this );
  171. #endif
  172. }
  173. private void CopyLog()
  174. {
  175. #if UNITY_EDITOR || !UNITY_WEBGL
  176. string log = GetCopyContent();
  177. if( !string.IsNullOrEmpty( log ) )
  178. GUIUtility.systemCopyBuffer = log;
  179. #endif
  180. }
  181. internal string GetCopyContent()
  182. {
  183. if( !logEntryTimestamp.HasValue )
  184. return logEntry.ToString();
  185. else
  186. {
  187. StringBuilder sb = listView.manager.sharedStringBuilder;
  188. sb.Length = 0;
  189. logEntryTimestamp.Value.AppendFullTimestamp( sb );
  190. sb.Append( ": " ).Append( logEntry.ToString() );
  191. return sb.ToString();
  192. }
  193. }
  194. /// Here, we're using <see cref="TMP_Text.GetRenderedValues(bool)"/> instead of <see cref="TMP_Text.preferredHeight"/> because the latter doesn't take
  195. /// <see cref="TMP_Text.maxVisibleCharacters"/> into account. However, for <see cref="TMP_Text.GetRenderedValues(bool)"/> to work, we need to give it
  196. /// enough space (increase log item's height) and let it regenerate its mesh <see cref="TMP_Text.ForceMeshUpdate"/>.
  197. public float CalculateExpandedHeight( DebugLogEntry logEntry, DebugLogEntryTimestamp? logEntryTimestamp )
  198. {
  199. string text = logText.text;
  200. Vector2 size = ( transform as RectTransform ).sizeDelta;
  201. ( transform as RectTransform ).sizeDelta = new Vector2( size.x, 10000f );
  202. SetText( logEntry, logEntryTimestamp, true );
  203. logText.ForceMeshUpdate();
  204. float result = logText.GetRenderedValues( true ).y + copyLogButtonHeight;
  205. ( transform as RectTransform ).sizeDelta = size;
  206. logText.text = text;
  207. return Mathf.Max( listView.ItemHeight, result );
  208. }
  209. // Return a string containing complete information about the debug entry
  210. public override string ToString()
  211. {
  212. return logEntry.ToString();
  213. }
  214. }
  215. }