392 lines
13 KiB
C#
392 lines
13 KiB
C#
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.UI;
|
|||
|
|
|||
|
// Handles the log items in an optimized way such that existing log items are
|
|||
|
// recycled within the list instead of creating a new log item at each chance
|
|||
|
namespace IngameDebugConsole
|
|||
|
{
|
|||
|
public class DebugLogRecycledListView : MonoBehaviour
|
|||
|
{
|
|||
|
#pragma warning disable 0649
|
|||
|
// Cached components
|
|||
|
[SerializeField]
|
|||
|
private RectTransform transformComponent;
|
|||
|
[SerializeField]
|
|||
|
private RectTransform viewportTransform;
|
|||
|
|
|||
|
[SerializeField]
|
|||
|
private Color logItemNormalColor1;
|
|||
|
[SerializeField]
|
|||
|
private Color logItemNormalColor2;
|
|||
|
[SerializeField]
|
|||
|
private Color logItemSelectedColor;
|
|||
|
#pragma warning restore 0649
|
|||
|
|
|||
|
internal DebugLogManager manager;
|
|||
|
private ScrollRect scrollView;
|
|||
|
|
|||
|
private float logItemHeight, _1OverLogItemHeight;
|
|||
|
private float viewportHeight;
|
|||
|
|
|||
|
// Unique debug entries
|
|||
|
private List<DebugLogEntry> collapsedLogEntries = null;
|
|||
|
|
|||
|
// Indices of debug entries to show in collapsedLogEntries
|
|||
|
private DebugLogIndexList<int> indicesOfEntriesToShow = null;
|
|||
|
private DebugLogIndexList<DebugLogEntryTimestamp> timestampsOfEntriesToShow = null;
|
|||
|
|
|||
|
private int indexOfSelectedLogEntry = int.MaxValue;
|
|||
|
private float positionOfSelectedLogEntry = float.MaxValue;
|
|||
|
private float heightOfSelectedLogEntry;
|
|||
|
private float deltaHeightOfSelectedLogEntry;
|
|||
|
|
|||
|
// Log items used to visualize the debug entries at specified indices
|
|||
|
private readonly Dictionary<int, DebugLogItem> logItemsAtIndices = new Dictionary<int, DebugLogItem>( 256 );
|
|||
|
|
|||
|
private bool isCollapseOn = false;
|
|||
|
|
|||
|
// Current indices of debug entries shown on screen
|
|||
|
private int currentTopIndex = -1, currentBottomIndex = -1;
|
|||
|
|
|||
|
public float ItemHeight { get { return logItemHeight; } }
|
|||
|
public float SelectedItemHeight { get { return heightOfSelectedLogEntry; } }
|
|||
|
|
|||
|
private void Awake()
|
|||
|
{
|
|||
|
scrollView = viewportTransform.GetComponentInParent<ScrollRect>();
|
|||
|
scrollView.onValueChanged.AddListener( ( pos ) => UpdateItemsInTheList( false ) );
|
|||
|
|
|||
|
viewportHeight = viewportTransform.rect.height;
|
|||
|
}
|
|||
|
|
|||
|
public void Initialize( DebugLogManager manager, List<DebugLogEntry> collapsedLogEntries, DebugLogIndexList<int> indicesOfEntriesToShow, DebugLogIndexList<DebugLogEntryTimestamp> timestampsOfEntriesToShow, float logItemHeight )
|
|||
|
{
|
|||
|
this.manager = manager;
|
|||
|
this.collapsedLogEntries = collapsedLogEntries;
|
|||
|
this.indicesOfEntriesToShow = indicesOfEntriesToShow;
|
|||
|
this.timestampsOfEntriesToShow = timestampsOfEntriesToShow;
|
|||
|
this.logItemHeight = logItemHeight;
|
|||
|
_1OverLogItemHeight = 1f / logItemHeight;
|
|||
|
}
|
|||
|
|
|||
|
public void SetCollapseMode( bool collapse )
|
|||
|
{
|
|||
|
isCollapseOn = collapse;
|
|||
|
}
|
|||
|
|
|||
|
// A log item is clicked, highlight it
|
|||
|
public void OnLogItemClicked( DebugLogItem item )
|
|||
|
{
|
|||
|
OnLogItemClickedInternal( item.Index, item );
|
|||
|
}
|
|||
|
|
|||
|
// Force expand the log item at specified index
|
|||
|
public void SelectAndFocusOnLogItemAtIndex( int itemIndex )
|
|||
|
{
|
|||
|
if( indexOfSelectedLogEntry != itemIndex ) // Make sure that we aren't deselecting the target log item
|
|||
|
OnLogItemClickedInternal( itemIndex );
|
|||
|
|
|||
|
float transformComponentCenterYAtTop = viewportHeight * 0.5f;
|
|||
|
float transformComponentCenterYAtBottom = transformComponent.sizeDelta.y - viewportHeight * 0.5f;
|
|||
|
float transformComponentTargetCenterY = itemIndex * logItemHeight + viewportHeight * 0.5f;
|
|||
|
if( transformComponentCenterYAtTop == transformComponentCenterYAtBottom )
|
|||
|
scrollView.verticalNormalizedPosition = 0.5f;
|
|||
|
else
|
|||
|
scrollView.verticalNormalizedPosition = Mathf.Clamp01( Mathf.InverseLerp( transformComponentCenterYAtBottom, transformComponentCenterYAtTop, transformComponentTargetCenterY ) );
|
|||
|
|
|||
|
manager.SetSnapToBottom( false );
|
|||
|
}
|
|||
|
|
|||
|
private void OnLogItemClickedInternal( int itemIndex, DebugLogItem referenceItem = null )
|
|||
|
{
|
|||
|
if( indexOfSelectedLogEntry != itemIndex )
|
|||
|
{
|
|||
|
DeselectSelectedLogItem();
|
|||
|
|
|||
|
if( !referenceItem )
|
|||
|
{
|
|||
|
if( currentTopIndex == -1 )
|
|||
|
UpdateItemsInTheList( false ); // Try to generate some DebugLogItems, we need one DebugLogItem to calculate the text height
|
|||
|
|
|||
|
referenceItem = logItemsAtIndices[currentTopIndex];
|
|||
|
}
|
|||
|
|
|||
|
indexOfSelectedLogEntry = itemIndex;
|
|||
|
positionOfSelectedLogEntry = itemIndex * logItemHeight;
|
|||
|
heightOfSelectedLogEntry = referenceItem.CalculateExpandedHeight( collapsedLogEntries[indicesOfEntriesToShow[itemIndex]], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[itemIndex] : (DebugLogEntryTimestamp?) null );
|
|||
|
deltaHeightOfSelectedLogEntry = heightOfSelectedLogEntry - logItemHeight;
|
|||
|
|
|||
|
manager.SetSnapToBottom( false );
|
|||
|
}
|
|||
|
else
|
|||
|
DeselectSelectedLogItem();
|
|||
|
|
|||
|
if( indexOfSelectedLogEntry >= currentTopIndex && indexOfSelectedLogEntry <= currentBottomIndex )
|
|||
|
ColorLogItem( logItemsAtIndices[indexOfSelectedLogEntry], indexOfSelectedLogEntry );
|
|||
|
|
|||
|
CalculateContentHeight();
|
|||
|
|
|||
|
HardResetItems();
|
|||
|
UpdateItemsInTheList( true );
|
|||
|
|
|||
|
manager.ValidateScrollPosition();
|
|||
|
}
|
|||
|
|
|||
|
// Deselect the currently selected log item
|
|||
|
public void DeselectSelectedLogItem()
|
|||
|
{
|
|||
|
int indexOfPreviouslySelectedLogEntry = indexOfSelectedLogEntry;
|
|||
|
indexOfSelectedLogEntry = int.MaxValue;
|
|||
|
|
|||
|
positionOfSelectedLogEntry = float.MaxValue;
|
|||
|
heightOfSelectedLogEntry = deltaHeightOfSelectedLogEntry = 0f;
|
|||
|
|
|||
|
if( indexOfPreviouslySelectedLogEntry >= currentTopIndex && indexOfPreviouslySelectedLogEntry <= currentBottomIndex )
|
|||
|
ColorLogItem( logItemsAtIndices[indexOfPreviouslySelectedLogEntry], indexOfPreviouslySelectedLogEntry );
|
|||
|
}
|
|||
|
|
|||
|
// Number of debug entries may be changed, update the list
|
|||
|
public void OnLogEntriesUpdated( bool updateAllVisibleItemContents )
|
|||
|
{
|
|||
|
CalculateContentHeight();
|
|||
|
viewportHeight = viewportTransform.rect.height;
|
|||
|
|
|||
|
if( updateAllVisibleItemContents )
|
|||
|
HardResetItems();
|
|||
|
|
|||
|
UpdateItemsInTheList( updateAllVisibleItemContents );
|
|||
|
}
|
|||
|
|
|||
|
// A single collapsed log entry at specified index is updated, refresh its item if visible
|
|||
|
public void OnCollapsedLogEntryAtIndexUpdated( int index )
|
|||
|
{
|
|||
|
DebugLogItem logItem;
|
|||
|
if( logItemsAtIndices.TryGetValue( index, out logItem ) )
|
|||
|
{
|
|||
|
logItem.ShowCount();
|
|||
|
|
|||
|
if( timestampsOfEntriesToShow != null )
|
|||
|
logItem.UpdateTimestamp( timestampsOfEntriesToShow[index] );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Log window's width has changed, update the expanded (currently selected) log's height
|
|||
|
public void OnViewportWidthChanged()
|
|||
|
{
|
|||
|
if( indexOfSelectedLogEntry >= indicesOfEntriesToShow.Count )
|
|||
|
return;
|
|||
|
|
|||
|
if( currentTopIndex == -1 )
|
|||
|
{
|
|||
|
UpdateItemsInTheList( false ); // Try to generate some DebugLogItems, we need one DebugLogItem to calculate the text height
|
|||
|
if( currentTopIndex == -1 ) // No DebugLogItems are generated, weird
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
DebugLogItem referenceItem = logItemsAtIndices[currentTopIndex];
|
|||
|
|
|||
|
heightOfSelectedLogEntry = referenceItem.CalculateExpandedHeight( collapsedLogEntries[indicesOfEntriesToShow[indexOfSelectedLogEntry]], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[indexOfSelectedLogEntry] : (DebugLogEntryTimestamp?) null );
|
|||
|
deltaHeightOfSelectedLogEntry = heightOfSelectedLogEntry - logItemHeight;
|
|||
|
|
|||
|
CalculateContentHeight();
|
|||
|
|
|||
|
HardResetItems();
|
|||
|
UpdateItemsInTheList( true );
|
|||
|
|
|||
|
manager.ValidateScrollPosition();
|
|||
|
}
|
|||
|
|
|||
|
// Log window's height has changed, update the list
|
|||
|
public void OnViewportHeightChanged()
|
|||
|
{
|
|||
|
viewportHeight = viewportTransform.rect.height;
|
|||
|
UpdateItemsInTheList( false );
|
|||
|
}
|
|||
|
|
|||
|
private void HardResetItems()
|
|||
|
{
|
|||
|
if( currentTopIndex != -1 )
|
|||
|
{
|
|||
|
DestroyLogItemsBetweenIndices( currentTopIndex, currentBottomIndex );
|
|||
|
currentTopIndex = -1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void CalculateContentHeight()
|
|||
|
{
|
|||
|
float newHeight = Mathf.Max( 1f, indicesOfEntriesToShow.Count * logItemHeight + deltaHeightOfSelectedLogEntry );
|
|||
|
transformComponent.sizeDelta = new Vector2( 0f, newHeight );
|
|||
|
}
|
|||
|
|
|||
|
// Calculate the indices of log entries to show
|
|||
|
// and handle log items accordingly
|
|||
|
public void UpdateItemsInTheList( bool updateAllVisibleItemContents )
|
|||
|
{
|
|||
|
// If there is at least one log entry to show
|
|||
|
if( indicesOfEntriesToShow.Count > 0 )
|
|||
|
{
|
|||
|
float contentPosTop = transformComponent.anchoredPosition.y - 1f;
|
|||
|
float contentPosBottom = contentPosTop + viewportHeight + 2f;
|
|||
|
|
|||
|
if( positionOfSelectedLogEntry <= contentPosBottom )
|
|||
|
{
|
|||
|
if( positionOfSelectedLogEntry <= contentPosTop )
|
|||
|
{
|
|||
|
contentPosTop -= deltaHeightOfSelectedLogEntry;
|
|||
|
contentPosBottom -= deltaHeightOfSelectedLogEntry;
|
|||
|
|
|||
|
if( contentPosTop < positionOfSelectedLogEntry - 1f )
|
|||
|
contentPosTop = positionOfSelectedLogEntry - 1f;
|
|||
|
|
|||
|
if( contentPosBottom < contentPosTop + 2f )
|
|||
|
contentPosBottom = contentPosTop + 2f;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
contentPosBottom -= deltaHeightOfSelectedLogEntry;
|
|||
|
if( contentPosBottom < positionOfSelectedLogEntry + 1f )
|
|||
|
contentPosBottom = positionOfSelectedLogEntry + 1f;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
int newTopIndex = (int) ( contentPosTop * _1OverLogItemHeight );
|
|||
|
int newBottomIndex = (int) ( contentPosBottom * _1OverLogItemHeight );
|
|||
|
|
|||
|
if( newTopIndex < 0 )
|
|||
|
newTopIndex = 0;
|
|||
|
|
|||
|
if( newBottomIndex > indicesOfEntriesToShow.Count - 1 )
|
|||
|
newBottomIndex = indicesOfEntriesToShow.Count - 1;
|
|||
|
|
|||
|
if( currentTopIndex == -1 )
|
|||
|
{
|
|||
|
// There are no log items visible on screen,
|
|||
|
// just create the new log items
|
|||
|
updateAllVisibleItemContents = true;
|
|||
|
|
|||
|
currentTopIndex = newTopIndex;
|
|||
|
currentBottomIndex = newBottomIndex;
|
|||
|
|
|||
|
CreateLogItemsBetweenIndices( newTopIndex, newBottomIndex );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// There are some log items visible on screen
|
|||
|
|
|||
|
if( newBottomIndex < currentTopIndex || newTopIndex > currentBottomIndex )
|
|||
|
{
|
|||
|
// If user scrolled a lot such that, none of the log items are now within
|
|||
|
// the bounds of the scroll view, pool all the previous log items and create
|
|||
|
// new log items for the new list of visible debug entries
|
|||
|
updateAllVisibleItemContents = true;
|
|||
|
|
|||
|
DestroyLogItemsBetweenIndices( currentTopIndex, currentBottomIndex );
|
|||
|
CreateLogItemsBetweenIndices( newTopIndex, newBottomIndex );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// User did not scroll a lot such that, there are still some log items within
|
|||
|
// the bounds of the scroll view. Don't destroy them but update their content,
|
|||
|
// if necessary
|
|||
|
if( newTopIndex > currentTopIndex )
|
|||
|
DestroyLogItemsBetweenIndices( currentTopIndex, newTopIndex - 1 );
|
|||
|
|
|||
|
if( newBottomIndex < currentBottomIndex )
|
|||
|
DestroyLogItemsBetweenIndices( newBottomIndex + 1, currentBottomIndex );
|
|||
|
|
|||
|
if( newTopIndex < currentTopIndex )
|
|||
|
{
|
|||
|
CreateLogItemsBetweenIndices( newTopIndex, currentTopIndex - 1 );
|
|||
|
|
|||
|
// If it is not necessary to update all the log items,
|
|||
|
// then just update the newly created log items. Otherwise,
|
|||
|
// wait for the major update
|
|||
|
if( !updateAllVisibleItemContents )
|
|||
|
UpdateLogItemContentsBetweenIndices( newTopIndex, currentTopIndex - 1 );
|
|||
|
}
|
|||
|
|
|||
|
if( newBottomIndex > currentBottomIndex )
|
|||
|
{
|
|||
|
CreateLogItemsBetweenIndices( currentBottomIndex + 1, newBottomIndex );
|
|||
|
|
|||
|
// If it is not necessary to update all the log items,
|
|||
|
// then just update the newly created log items. Otherwise,
|
|||
|
// wait for the major update
|
|||
|
if( !updateAllVisibleItemContents )
|
|||
|
UpdateLogItemContentsBetweenIndices( currentBottomIndex + 1, newBottomIndex );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
currentTopIndex = newTopIndex;
|
|||
|
currentBottomIndex = newBottomIndex;
|
|||
|
}
|
|||
|
|
|||
|
if( updateAllVisibleItemContents )
|
|||
|
{
|
|||
|
// Update all the log items
|
|||
|
UpdateLogItemContentsBetweenIndices( currentTopIndex, currentBottomIndex );
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
HardResetItems();
|
|||
|
}
|
|||
|
|
|||
|
private void CreateLogItemsBetweenIndices( int topIndex, int bottomIndex )
|
|||
|
{
|
|||
|
for( int i = topIndex; i <= bottomIndex; i++ )
|
|||
|
CreateLogItemAtIndex( i );
|
|||
|
}
|
|||
|
|
|||
|
// Create (or unpool) a log item
|
|||
|
private void CreateLogItemAtIndex( int index )
|
|||
|
{
|
|||
|
DebugLogItem logItem = manager.PopLogItem();
|
|||
|
|
|||
|
// Reposition the log item
|
|||
|
Vector2 anchoredPosition = new Vector2( 1f, -index * logItemHeight );
|
|||
|
if( index > indexOfSelectedLogEntry )
|
|||
|
anchoredPosition.y -= deltaHeightOfSelectedLogEntry;
|
|||
|
|
|||
|
logItem.Transform.anchoredPosition = anchoredPosition;
|
|||
|
|
|||
|
// Color the log item
|
|||
|
ColorLogItem( logItem, index );
|
|||
|
|
|||
|
// To access this log item easily in the future, add it to the dictionary
|
|||
|
logItemsAtIndices[index] = logItem;
|
|||
|
}
|
|||
|
|
|||
|
private void DestroyLogItemsBetweenIndices( int topIndex, int bottomIndex )
|
|||
|
{
|
|||
|
for( int i = topIndex; i <= bottomIndex; i++ )
|
|||
|
manager.PoolLogItem( logItemsAtIndices[i] );
|
|||
|
}
|
|||
|
|
|||
|
private void UpdateLogItemContentsBetweenIndices( int topIndex, int bottomIndex )
|
|||
|
{
|
|||
|
DebugLogItem logItem;
|
|||
|
for( int i = topIndex; i <= bottomIndex; i++ )
|
|||
|
{
|
|||
|
logItem = logItemsAtIndices[i];
|
|||
|
logItem.SetContent( collapsedLogEntries[indicesOfEntriesToShow[i]], ( timestampsOfEntriesToShow != null ) ? timestampsOfEntriesToShow[i] : (DebugLogEntryTimestamp?) null, i, i == indexOfSelectedLogEntry );
|
|||
|
|
|||
|
if( isCollapseOn )
|
|||
|
logItem.ShowCount();
|
|||
|
else
|
|||
|
logItem.HideCount();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Color a log item using its index
|
|||
|
private void ColorLogItem( DebugLogItem logItem, int index )
|
|||
|
{
|
|||
|
if( index == indexOfSelectedLogEntry )
|
|||
|
logItem.Image.color = logItemSelectedColor;
|
|||
|
else if( index % 2 == 0 )
|
|||
|
logItem.Image.color = logItemNormalColor1;
|
|||
|
else
|
|||
|
logItem.Image.color = logItemNormalColor2;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|