DebugLogRecycledListView.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.UI;
  4. // Handles the log items in an optimized way such that existing log items are
  5. // recycled within the list instead of creating a new log item at each chance
  6. namespace IngameDebugConsole
  7. {
  8. public class DebugLogRecycledListView : MonoBehaviour
  9. {
  10. #pragma warning disable 0649
  11. // Cached components
  12. [SerializeField]
  13. private RectTransform transformComponent;
  14. [SerializeField]
  15. private RectTransform viewportTransform;
  16. [SerializeField]
  17. private Color logItemNormalColor1;
  18. [SerializeField]
  19. private Color logItemNormalColor2;
  20. [SerializeField]
  21. private Color logItemSelectedColor;
  22. #pragma warning restore 0649
  23. internal DebugLogManager manager;
  24. private ScrollRect scrollView;
  25. private float logItemHeight, _1OverLogItemHeight;
  26. private float viewportHeight;
  27. // Unique debug entries
  28. private List<DebugLogEntry> collapsedLogEntries = null;
  29. // Indices of debug entries to show in collapsedLogEntries
  30. private DebugLogIndexList<int> indicesOfEntriesToShow = null;
  31. private DebugLogIndexList<DebugLogEntryTimestamp> timestampsOfEntriesToShow = null;
  32. private int indexOfSelectedLogEntry = int.MaxValue;
  33. private float positionOfSelectedLogEntry = float.MaxValue;
  34. private float heightOfSelectedLogEntry;
  35. private float deltaHeightOfSelectedLogEntry;
  36. // Log items used to visualize the debug entries at specified indices
  37. private readonly Dictionary<int, DebugLogItem> logItemsAtIndices = new Dictionary<int, DebugLogItem>( 256 );
  38. private bool isCollapseOn = false;
  39. // Current indices of debug entries shown on screen
  40. private int currentTopIndex = -1, currentBottomIndex = -1;
  41. public float ItemHeight { get { return logItemHeight; } }
  42. public float SelectedItemHeight { get { return heightOfSelectedLogEntry; } }
  43. private void Awake()
  44. {
  45. scrollView = viewportTransform.GetComponentInParent<ScrollRect>();
  46. scrollView.onValueChanged.AddListener( ( pos ) => UpdateItemsInTheList( false ) );
  47. viewportHeight = viewportTransform.rect.height;
  48. }
  49. public void Initialize( DebugLogManager manager, List<DebugLogEntry> collapsedLogEntries, DebugLogIndexList<int> indicesOfEntriesToShow, DebugLogIndexList<DebugLogEntryTimestamp> timestampsOfEntriesToShow, float logItemHeight )
  50. {
  51. this.manager = manager;
  52. this.collapsedLogEntries = collapsedLogEntries;
  53. this.indicesOfEntriesToShow = indicesOfEntriesToShow;
  54. this.timestampsOfEntriesToShow = timestampsOfEntriesToShow;
  55. this.logItemHeight = logItemHeight;
  56. _1OverLogItemHeight = 1f / logItemHeight;
  57. }
  58. public void SetCollapseMode( bool collapse )
  59. {
  60. isCollapseOn = collapse;
  61. }
  62. // A log item is clicked, highlight it
  63. public void OnLogItemClicked( DebugLogItem item )
  64. {
  65. OnLogItemClickedInternal( item.Index, item );
  66. }
  67. // Force expand the log item at specified index
  68. public void SelectAndFocusOnLogItemAtIndex( int itemIndex )
  69. {
  70. if( indexOfSelectedLogEntry != itemIndex ) // Make sure that we aren't deselecting the target log item
  71. OnLogItemClickedInternal( itemIndex );
  72. float transformComponentCenterYAtTop = viewportHeight * 0.5f;
  73. float transformComponentCenterYAtBottom = transformComponent.sizeDelta.y - viewportHeight * 0.5f;
  74. float transformComponentTargetCenterY = itemIndex * logItemHeight + viewportHeight * 0.5f;
  75. if( transformComponentCenterYAtTop == transformComponentCenterYAtBottom )
  76. scrollView.verticalNormalizedPosition = 0.5f;
  77. else
  78. scrollView.verticalNormalizedPosition = Mathf.Clamp01( Mathf.InverseLerp( transformComponentCenterYAtBottom, transformComponentCenterYAtTop, transformComponentTargetCenterY ) );
  79. manager.SetSnapToBottom( false );
  80. }
  81. private void OnLogItemClickedInternal( int itemIndex, DebugLogItem referenceItem = null )
  82. {
  83. if( indexOfSelectedLogEntry != itemIndex )
  84. {
  85. DeselectSelectedLogItem();
  86. if( !referenceItem )
  87. {
  88. if( currentTopIndex == -1 )
  89. UpdateItemsInTheList( false ); // Try to generate some DebugLogItems, we need one DebugLogItem to calculate the text height
  90. referenceItem = logItemsAtIndices[currentTopIndex];
  91. }
  92. indexOfSelectedLogEntry = itemIndex;
  93. positionOfSelectedLogEntry = itemIndex * logItemHeight;
  94. heightOfSelectedLogEntry = referenceItem.CalculateExpandedHeight( collapsedLogEntries[indicesOfEntriesToShow[itemIndex]], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[itemIndex] : (DebugLogEntryTimestamp?) null );
  95. deltaHeightOfSelectedLogEntry = heightOfSelectedLogEntry - logItemHeight;
  96. manager.SetSnapToBottom( false );
  97. }
  98. else
  99. DeselectSelectedLogItem();
  100. if( indexOfSelectedLogEntry >= currentTopIndex && indexOfSelectedLogEntry <= currentBottomIndex )
  101. ColorLogItem( logItemsAtIndices[indexOfSelectedLogEntry], indexOfSelectedLogEntry );
  102. CalculateContentHeight();
  103. HardResetItems();
  104. UpdateItemsInTheList( true );
  105. manager.ValidateScrollPosition();
  106. }
  107. // Deselect the currently selected log item
  108. public void DeselectSelectedLogItem()
  109. {
  110. int indexOfPreviouslySelectedLogEntry = indexOfSelectedLogEntry;
  111. indexOfSelectedLogEntry = int.MaxValue;
  112. positionOfSelectedLogEntry = float.MaxValue;
  113. heightOfSelectedLogEntry = deltaHeightOfSelectedLogEntry = 0f;
  114. if( indexOfPreviouslySelectedLogEntry >= currentTopIndex && indexOfPreviouslySelectedLogEntry <= currentBottomIndex )
  115. ColorLogItem( logItemsAtIndices[indexOfPreviouslySelectedLogEntry], indexOfPreviouslySelectedLogEntry );
  116. }
  117. // Number of debug entries may be changed, update the list
  118. public void OnLogEntriesUpdated( bool updateAllVisibleItemContents )
  119. {
  120. CalculateContentHeight();
  121. viewportHeight = viewportTransform.rect.height;
  122. if( updateAllVisibleItemContents )
  123. HardResetItems();
  124. UpdateItemsInTheList( updateAllVisibleItemContents );
  125. }
  126. // A single collapsed log entry at specified index is updated, refresh its item if visible
  127. public void OnCollapsedLogEntryAtIndexUpdated( int index )
  128. {
  129. DebugLogItem logItem;
  130. if( logItemsAtIndices.TryGetValue( index, out logItem ) )
  131. {
  132. logItem.ShowCount();
  133. if( timestampsOfEntriesToShow != null )
  134. logItem.UpdateTimestamp( timestampsOfEntriesToShow[index] );
  135. }
  136. }
  137. // Log window's width has changed, update the expanded (currently selected) log's height
  138. public void OnViewportWidthChanged()
  139. {
  140. if( indexOfSelectedLogEntry >= indicesOfEntriesToShow.Count )
  141. return;
  142. if( currentTopIndex == -1 )
  143. {
  144. UpdateItemsInTheList( false ); // Try to generate some DebugLogItems, we need one DebugLogItem to calculate the text height
  145. if( currentTopIndex == -1 ) // No DebugLogItems are generated, weird
  146. return;
  147. }
  148. DebugLogItem referenceItem = logItemsAtIndices[currentTopIndex];
  149. heightOfSelectedLogEntry = referenceItem.CalculateExpandedHeight( collapsedLogEntries[indicesOfEntriesToShow[indexOfSelectedLogEntry]], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[indexOfSelectedLogEntry] : (DebugLogEntryTimestamp?) null );
  150. deltaHeightOfSelectedLogEntry = heightOfSelectedLogEntry - logItemHeight;
  151. CalculateContentHeight();
  152. HardResetItems();
  153. UpdateItemsInTheList( true );
  154. manager.ValidateScrollPosition();
  155. }
  156. // Log window's height has changed, update the list
  157. public void OnViewportHeightChanged()
  158. {
  159. viewportHeight = viewportTransform.rect.height;
  160. UpdateItemsInTheList( false );
  161. }
  162. private void HardResetItems()
  163. {
  164. if( currentTopIndex != -1 )
  165. {
  166. DestroyLogItemsBetweenIndices( currentTopIndex, currentBottomIndex );
  167. currentTopIndex = -1;
  168. }
  169. }
  170. private void CalculateContentHeight()
  171. {
  172. float newHeight = Mathf.Max( 1f, indicesOfEntriesToShow.Count * logItemHeight + deltaHeightOfSelectedLogEntry );
  173. transformComponent.sizeDelta = new Vector2( 0f, newHeight );
  174. }
  175. // Calculate the indices of log entries to show
  176. // and handle log items accordingly
  177. public void UpdateItemsInTheList( bool updateAllVisibleItemContents )
  178. {
  179. // If there is at least one log entry to show
  180. if( indicesOfEntriesToShow.Count > 0 )
  181. {
  182. float contentPosTop = transformComponent.anchoredPosition.y - 1f;
  183. float contentPosBottom = contentPosTop + viewportHeight + 2f;
  184. if( positionOfSelectedLogEntry <= contentPosBottom )
  185. {
  186. if( positionOfSelectedLogEntry <= contentPosTop )
  187. {
  188. contentPosTop -= deltaHeightOfSelectedLogEntry;
  189. contentPosBottom -= deltaHeightOfSelectedLogEntry;
  190. if( contentPosTop < positionOfSelectedLogEntry - 1f )
  191. contentPosTop = positionOfSelectedLogEntry - 1f;
  192. if( contentPosBottom < contentPosTop + 2f )
  193. contentPosBottom = contentPosTop + 2f;
  194. }
  195. else
  196. {
  197. contentPosBottom -= deltaHeightOfSelectedLogEntry;
  198. if( contentPosBottom < positionOfSelectedLogEntry + 1f )
  199. contentPosBottom = positionOfSelectedLogEntry + 1f;
  200. }
  201. }
  202. int newTopIndex = (int) ( contentPosTop * _1OverLogItemHeight );
  203. int newBottomIndex = (int) ( contentPosBottom * _1OverLogItemHeight );
  204. if( newTopIndex < 0 )
  205. newTopIndex = 0;
  206. if( newBottomIndex > indicesOfEntriesToShow.Count - 1 )
  207. newBottomIndex = indicesOfEntriesToShow.Count - 1;
  208. if( currentTopIndex == -1 )
  209. {
  210. // There are no log items visible on screen,
  211. // just create the new log items
  212. updateAllVisibleItemContents = true;
  213. currentTopIndex = newTopIndex;
  214. currentBottomIndex = newBottomIndex;
  215. CreateLogItemsBetweenIndices( newTopIndex, newBottomIndex );
  216. }
  217. else
  218. {
  219. // There are some log items visible on screen
  220. if( newBottomIndex < currentTopIndex || newTopIndex > currentBottomIndex )
  221. {
  222. // If user scrolled a lot such that, none of the log items are now within
  223. // the bounds of the scroll view, pool all the previous log items and create
  224. // new log items for the new list of visible debug entries
  225. updateAllVisibleItemContents = true;
  226. DestroyLogItemsBetweenIndices( currentTopIndex, currentBottomIndex );
  227. CreateLogItemsBetweenIndices( newTopIndex, newBottomIndex );
  228. }
  229. else
  230. {
  231. // User did not scroll a lot such that, there are still some log items within
  232. // the bounds of the scroll view. Don't destroy them but update their content,
  233. // if necessary
  234. if( newTopIndex > currentTopIndex )
  235. DestroyLogItemsBetweenIndices( currentTopIndex, newTopIndex - 1 );
  236. if( newBottomIndex < currentBottomIndex )
  237. DestroyLogItemsBetweenIndices( newBottomIndex + 1, currentBottomIndex );
  238. if( newTopIndex < currentTopIndex )
  239. {
  240. CreateLogItemsBetweenIndices( newTopIndex, currentTopIndex - 1 );
  241. // If it is not necessary to update all the log items,
  242. // then just update the newly created log items. Otherwise,
  243. // wait for the major update
  244. if( !updateAllVisibleItemContents )
  245. UpdateLogItemContentsBetweenIndices( newTopIndex, currentTopIndex - 1 );
  246. }
  247. if( newBottomIndex > currentBottomIndex )
  248. {
  249. CreateLogItemsBetweenIndices( currentBottomIndex + 1, newBottomIndex );
  250. // If it is not necessary to update all the log items,
  251. // then just update the newly created log items. Otherwise,
  252. // wait for the major update
  253. if( !updateAllVisibleItemContents )
  254. UpdateLogItemContentsBetweenIndices( currentBottomIndex + 1, newBottomIndex );
  255. }
  256. }
  257. currentTopIndex = newTopIndex;
  258. currentBottomIndex = newBottomIndex;
  259. }
  260. if( updateAllVisibleItemContents )
  261. {
  262. // Update all the log items
  263. UpdateLogItemContentsBetweenIndices( currentTopIndex, currentBottomIndex );
  264. }
  265. }
  266. else
  267. HardResetItems();
  268. }
  269. private void CreateLogItemsBetweenIndices( int topIndex, int bottomIndex )
  270. {
  271. for( int i = topIndex; i <= bottomIndex; i++ )
  272. CreateLogItemAtIndex( i );
  273. }
  274. // Create (or unpool) a log item
  275. private void CreateLogItemAtIndex( int index )
  276. {
  277. DebugLogItem logItem = manager.PopLogItem();
  278. // Reposition the log item
  279. Vector2 anchoredPosition = new Vector2( 1f, -index * logItemHeight );
  280. if( index > indexOfSelectedLogEntry )
  281. anchoredPosition.y -= deltaHeightOfSelectedLogEntry;
  282. logItem.Transform.anchoredPosition = anchoredPosition;
  283. // Color the log item
  284. ColorLogItem( logItem, index );
  285. // To access this log item easily in the future, add it to the dictionary
  286. logItemsAtIndices[index] = logItem;
  287. }
  288. private void DestroyLogItemsBetweenIndices( int topIndex, int bottomIndex )
  289. {
  290. for( int i = topIndex; i <= bottomIndex; i++ )
  291. manager.PoolLogItem( logItemsAtIndices[i] );
  292. }
  293. private void UpdateLogItemContentsBetweenIndices( int topIndex, int bottomIndex )
  294. {
  295. DebugLogItem logItem;
  296. for( int i = topIndex; i <= bottomIndex; i++ )
  297. {
  298. logItem = logItemsAtIndices[i];
  299. logItem.SetContent( collapsedLogEntries[indicesOfEntriesToShow[i]], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[i] : (DebugLogEntryTimestamp?) null, i, i == indexOfSelectedLogEntry );
  300. if( isCollapseOn )
  301. logItem.ShowCount();
  302. else
  303. logItem.HideCount();
  304. }
  305. }
  306. // Color a log item using its index
  307. private void ColorLogItem( DebugLogItem logItem, int index )
  308. {
  309. if( index == indexOfSelectedLogEntry )
  310. logItem.Image.color = logItemSelectedColor;
  311. else if( index % 2 == 0 )
  312. logItem.Image.color = logItemNormalColor1;
  313. else
  314. logItem.Image.color = logItemNormalColor2;
  315. }
  316. }
  317. }