DebugLogRecycledListView.cs 10 KB

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