1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051 |
- using UnityEngine;
- using UnityEngine.UI;
- using UnityEngine.EventSystems;
- using System.Collections.Generic;
- using System.IO;
- // Receives debug entries and custom events (e.g. Clear, Collapse, Filter by Type)
- // and notifies the recycled list view of changes to the list of debug entries
- //
- // - Vocabulary -
- // Debug/Log entry: a Debug.Log/LogError/LogWarning/LogException/LogAssertion request made by
- // the client and intercepted by this manager object
- // Debug/Log item: a visual (uGUI) representation of a debug entry
- //
- // There can be a lot of debug entries in the system but there will only be a handful of log items
- // to show their properties on screen (these log items are recycled as the list is scrolled)
- // An enum to represent filtered log types
- namespace IngameDebugConsole
- {
- public enum DebugLogFilter
- {
- None = 0,
- Info = 1,
- Warning = 2,
- Error = 4,
- All = 7
- }
- public class DebugLogManager : MonoBehaviour
- {
- private static DebugLogManager instance = null;
- #pragma warning disable 0649
- // Debug console will persist between scenes
- [Header( "Properties" )]
- [SerializeField]
- [HideInInspector]
- private bool singleton = true;
- // Minimum height of the console window
- [SerializeField]
- [HideInInspector]
- private float minimumHeight = 200f;
- [SerializeField]
- [HideInInspector]
- private bool enablePopup = true;
- [SerializeField]
- [HideInInspector]
- private bool startInPopupMode = true;
- [SerializeField]
- [HideInInspector]
- private bool startMinimized = false;
- [SerializeField]
- [HideInInspector]
- private bool toggleWithKey = false;
- [SerializeField]
- [HideInInspector]
- private KeyCode toggleKey = KeyCode.BackQuote;
- [SerializeField]
- [HideInInspector]
- private bool enableSearchbar = true;
- [SerializeField]
- [HideInInspector]
- private float topSearchbarMinWidth = 360f;
- // Should command input field be cleared after pressing Enter
- [SerializeField]
- [HideInInspector]
- private bool clearCommandAfterExecution = true;
- [SerializeField]
- [HideInInspector]
- private int commandHistorySize = 15;
- [SerializeField]
- [HideInInspector]
- private bool receiveLogcatLogsInAndroid = false;
- [SerializeField]
- [HideInInspector]
- private string logcatArguments;
- [SerializeField]
- private bool avoidScreenCutout = true;
- [SerializeField]
- private int maxLogLength = 10000;
- [Header( "Visuals" )]
- [SerializeField]
- private DebugLogItem logItemPrefab;
- // Visuals for different log types
- [SerializeField]
- private Sprite infoLog;
- [SerializeField]
- private Sprite warningLog;
- [SerializeField]
- private Sprite errorLog;
- private Dictionary<LogType, Sprite> logSpriteRepresentations;
- [SerializeField]
- private Color collapseButtonNormalColor;
- [SerializeField]
- private Color collapseButtonSelectedColor;
- [SerializeField]
- private Color filterButtonsNormalColor;
- [SerializeField]
- private Color filterButtonsSelectedColor;
- [Header( "Internal References" )]
- [SerializeField]
- private RectTransform logWindowTR;
- private RectTransform canvasTR;
- [SerializeField]
- private RectTransform logItemsContainer;
- [SerializeField]
- private InputField commandInputField;
- [SerializeField]
- private Image collapseButton;
- [SerializeField]
- private Image filterInfoButton;
- [SerializeField]
- private Image filterWarningButton;
- [SerializeField]
- private Image filterErrorButton;
- [SerializeField]
- private Text infoEntryCountText;
- [SerializeField]
- private Text warningEntryCountText;
- [SerializeField]
- private Text errorEntryCountText;
- [SerializeField]
- private RectTransform searchbar;
- [SerializeField]
- private RectTransform searchbarSlotTop;
- [SerializeField]
- private RectTransform searchbarSlotBottom;
- [SerializeField]
- private GameObject snapToBottomButton;
- // Canvas group to modify visibility of the log window
- [SerializeField]
- private CanvasGroup logWindowCanvasGroup;
- [SerializeField]
- private DebugLogPopup popupManager;
- [SerializeField]
- private ScrollRect logItemsScrollRect;
- private RectTransform logItemsScrollRectTR;
- private Vector2 logItemsScrollRectOriginalSize;
- // Recycled list view to handle the log items efficiently
- [SerializeField]
- private DebugLogRecycledListView recycledListView;
- #pragma warning restore 0649
- // Number of entries filtered by their types
- private int infoEntryCount = 0, warningEntryCount = 0, errorEntryCount = 0;
- private bool isLogWindowVisible = true;
- private bool screenDimensionsChanged = true;
- // Filters to apply to the list of debug entries to show
- private bool isCollapseOn = false;
- private DebugLogFilter logFilter = DebugLogFilter.All;
- // Search filter
- private string searchTerm;
- private bool isInSearchMode;
- // If the last log item is completely visible (scrollbar is at the bottom),
- // scrollbar will remain at the bottom when new debug entries are received
- private bool snapToBottom = true;
- // List of unique debug entries (duplicates of entries are not kept)
- private List<DebugLogEntry> collapsedLogEntries;
- // Dictionary to quickly find if a log already exists in collapsedLogEntries
- private Dictionary<DebugLogEntry, int> collapsedLogEntriesMap;
- // The order the collapsedLogEntries are received
- // (duplicate entries have the same index (value))
- private DebugLogIndexList uncollapsedLogEntriesIndices;
- // Filtered list of debug entries to show
- private DebugLogIndexList indicesOfListEntriesToShow;
- // Logs that should be registered in Update-loop
- private DynamicCircularBuffer<QueuedDebugLogEntry> queuedLogEntries;
- private object logEntriesLock;
- // Pools for memory efficiency
- private List<DebugLogEntry> pooledLogEntries;
- private List<DebugLogItem> pooledLogItems;
- // History of the previously entered commands
- private CircularBuffer<string> commandHistory;
- private int commandHistoryIndex = -1;
- // Required in ValidateScrollPosition() function
- private PointerEventData nullPointerEventData;
- #if UNITY_EDITOR
- private bool isQuittingApplication;
- #endif
- #if !UNITY_EDITOR && UNITY_ANDROID
- private DebugLogLogcatListener logcatListener;
- #endif
- private CanvasGroup m_CanvasGroup = null;
- public void ShowOrHide(bool value) {
- if (value) {
- m_CanvasGroup.alpha = 1;
- m_CanvasGroup.blocksRaycasts = true;
- m_CanvasGroup.interactable = true;
- } else {
- m_CanvasGroup.alpha = 0;
- m_CanvasGroup.blocksRaycasts = false;
- m_CanvasGroup.interactable = false;
- }
- }
- private void Awake()
- {
- // Only one instance of debug console is allowed
- if( instance == null )
- {
- instance = this;
- // If it is a singleton object, don't destroy it between scene changes
- if( singleton )
- DontDestroyOnLoad( gameObject );
- }
- else if( this != instance )
- {
- Destroy( gameObject );
- return;
- }
- pooledLogEntries = new List<DebugLogEntry>( 16 );
- pooledLogItems = new List<DebugLogItem>( 16 );
- queuedLogEntries = new DynamicCircularBuffer<QueuedDebugLogEntry>( 16 );
- commandHistory = new CircularBuffer<string>( commandHistorySize );
- logEntriesLock = new object();
- canvasTR = (RectTransform) transform;
- logItemsScrollRectTR = (RectTransform) logItemsScrollRect.transform;
- logItemsScrollRectOriginalSize = logItemsScrollRectTR.sizeDelta;
- // Associate sprites with log types
- logSpriteRepresentations = new Dictionary<LogType, Sprite>()
- {
- { LogType.Log, infoLog },
- { LogType.Warning, warningLog },
- { LogType.Error, errorLog },
- { LogType.Exception, errorLog },
- { LogType.Assert, errorLog }
- };
- // Initially, all log types are visible
- filterInfoButton.color = filterButtonsSelectedColor;
- filterWarningButton.color = filterButtonsSelectedColor;
- filterErrorButton.color = filterButtonsSelectedColor;
- collapsedLogEntries = new List<DebugLogEntry>( 128 );
- collapsedLogEntriesMap = new Dictionary<DebugLogEntry, int>( 128 );
- uncollapsedLogEntriesIndices = new DebugLogIndexList();
- indicesOfListEntriesToShow = new DebugLogIndexList();
- recycledListView.Initialize( this, collapsedLogEntries, indicesOfListEntriesToShow, logItemPrefab.Transform.sizeDelta.y );
- recycledListView.UpdateItemsInTheList( true );
- if( minimumHeight < 200f )
- minimumHeight = 200f;
- if( !enableSearchbar )
- {
- searchbar = null;
- searchbarSlotTop.gameObject.SetActive( false );
- searchbarSlotBottom.gameObject.SetActive( false );
- }
- nullPointerEventData = new PointerEventData( null );
- m_CanvasGroup = GetComponent<CanvasGroup>();
- }
- private void OnEnable()
- {
- // Intercept debug entries
- Application.logMessageReceivedThreaded -= ReceivedLog;
- Application.logMessageReceivedThreaded += ReceivedLog;
- // Listen for entered commands
- commandInputField.onValidateInput -= OnValidateCommand;
- commandInputField.onValidateInput += OnValidateCommand;
- if( receiveLogcatLogsInAndroid )
- {
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( logcatListener == null )
- logcatListener = new DebugLogLogcatListener();
- logcatListener.Start( logcatArguments );
- #endif
- }
- DebugLogConsole.AddCommand( "save_logs", "Saves logs to a file", SaveLogsToFile );
- //Debug.LogAssertion( "assert" );
- //Debug.LogError( "error" );
- //Debug.LogException( new System.IO.EndOfStreamException() );
- //Debug.LogWarning( "warning" );
- //Debug.Log( "log" );
- }
- private void OnDisable()
- {
- if( instance != this )
- return;
- // Stop receiving debug entries
- Application.logMessageReceivedThreaded -= ReceivedLog;
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( logcatListener != null )
- logcatListener.Stop();
- #endif
- // Stop receiving commands
- commandInputField.onValidateInput -= OnValidateCommand;
- DebugLogConsole.RemoveCommand( "save_logs" );
- }
- private void Start()
- {
- if( ( enablePopup && startInPopupMode ) || ( !enablePopup && startMinimized ) )
- ShowPopup();
- else
- ShowLogWindow();
- popupManager.gameObject.SetActive( enablePopup );
- }
- #if UNITY_EDITOR
- private void OnApplicationQuit()
- {
- isQuittingApplication = true;
- }
- #endif
- // Window is resized, update the list
- private void OnRectTransformDimensionsChange()
- {
- screenDimensionsChanged = true;
- }
- private void LateUpdate()
- {
- #if UNITY_EDITOR
- if( isQuittingApplication )
- return;
- #endif
- int queuedLogCount = queuedLogEntries.Count;
- if( queuedLogCount > 0 )
- {
- for( int i = 0; i < queuedLogCount; i++ )
- {
- QueuedDebugLogEntry logEntry;
- lock( logEntriesLock )
- {
- logEntry = queuedLogEntries.RemoveFirst();
- }
- ProcessLog( logEntry );
- }
- }
- if( screenDimensionsChanged )
- {
- // Update the recycled list view
- if( isLogWindowVisible )
- recycledListView.OnViewportDimensionsChanged();
- else
- popupManager.OnViewportDimensionsChanged();
- #if UNITY_ANDROID || UNITY_IOS
- CheckScreenCutout();
- #endif
- if( searchbar )
- {
- float logWindowWidth = logWindowTR.rect.width;
- if( logWindowWidth >= topSearchbarMinWidth )
- {
- if( searchbar.parent == searchbarSlotBottom )
- {
- searchbarSlotTop.gameObject.SetActive( true );
- searchbar.SetParent( searchbarSlotTop, false );
- searchbarSlotBottom.gameObject.SetActive( false );
- logItemsScrollRectTR.anchoredPosition = Vector2.zero;
- logItemsScrollRectTR.sizeDelta = logItemsScrollRectOriginalSize;
- }
- }
- else
- {
- if( searchbar.parent == searchbarSlotTop )
- {
- searchbarSlotBottom.gameObject.SetActive( true );
- searchbar.SetParent( searchbarSlotBottom, false );
- searchbarSlotTop.gameObject.SetActive( false );
- float searchbarHeight = searchbarSlotBottom.sizeDelta.y;
- logItemsScrollRectTR.anchoredPosition = new Vector2( 0f, searchbarHeight * -0.5f );
- logItemsScrollRectTR.sizeDelta = logItemsScrollRectOriginalSize - new Vector2( 0f, searchbarHeight );
- }
- }
- }
- screenDimensionsChanged = false;
- }
- // If snapToBottom is enabled, force the scrollbar to the bottom
- if( snapToBottom )
- {
- logItemsScrollRect.verticalNormalizedPosition = 0f;
- if( snapToBottomButton.activeSelf )
- snapToBottomButton.SetActive( false );
- }
- else
- {
- float scrollPos = logItemsScrollRect.verticalNormalizedPosition;
- if( snapToBottomButton.activeSelf != ( scrollPos > 1E-6f && scrollPos < 0.9999f ) )
- snapToBottomButton.SetActive( !snapToBottomButton.activeSelf );
- }
- if( toggleWithKey )
- {
- if( Input.GetKeyDown( toggleKey ) )
- {
- if( isLogWindowVisible )
- ShowPopup();
- else
- ShowLogWindow();
- }
- }
- if( isLogWindowVisible && commandInputField.isFocused )
- {
- if( Input.GetKeyDown( KeyCode.UpArrow ) )
- {
- if( commandHistoryIndex == -1 )
- commandHistoryIndex = commandHistory.Count - 1;
- else if( --commandHistoryIndex < 0 )
- commandHistoryIndex = 0;
- if( commandHistoryIndex >= 0 && commandHistoryIndex < commandHistory.Count )
- {
- commandInputField.text = commandHistory[commandHistoryIndex];
- commandInputField.caretPosition = commandInputField.text.Length;
- }
- }
- else if( Input.GetKeyDown( KeyCode.DownArrow ) )
- {
- if( commandHistoryIndex == -1 )
- commandHistoryIndex = commandHistory.Count - 1;
- else if( ++commandHistoryIndex >= commandHistory.Count )
- commandHistoryIndex = commandHistory.Count - 1;
- if( commandHistoryIndex >= 0 && commandHistoryIndex < commandHistory.Count )
- commandInputField.text = commandHistory[commandHistoryIndex];
- }
- }
- #if !UNITY_EDITOR && UNITY_ANDROID
- if( logcatListener != null )
- {
- string log;
- while( ( log = logcatListener.GetLog() ) != null )
- ReceivedLog( "LOGCAT: " + log, string.Empty, LogType.Log );
- }
- #endif
- }
- public void ShowLogWindow()
- {
- // Show the log window
- logWindowCanvasGroup.interactable = true;
- logWindowCanvasGroup.blocksRaycasts = true;
- logWindowCanvasGroup.alpha = 1f;
- popupManager.Hide();
- // Update the recycled list view
- // (in case new entries were intercepted while log window was hidden)
- recycledListView.OnLogEntriesUpdated( true );
- isLogWindowVisible = true;
- }
- public void ShowPopup()
- {
- // Hide the log window
- logWindowCanvasGroup.interactable = false;
- logWindowCanvasGroup.blocksRaycasts = false;
- logWindowCanvasGroup.alpha = 0f;
- popupManager.Show();
- commandHistoryIndex = -1;
- isLogWindowVisible = false;
- }
- // Command field input is changed, check if command is submitted
- public char OnValidateCommand( string text, int charIndex, char addedChar )
- {
- if( addedChar == '\t' ) // Autocomplete attempt
- {
- if( !string.IsNullOrEmpty( text ) )
- {
- string autoCompletedCommand = DebugLogConsole.GetAutoCompleteCommand( text );
- if( !string.IsNullOrEmpty( autoCompletedCommand ) )
- commandInputField.text = autoCompletedCommand;
- }
- return '\0';
- }
- else if( addedChar == '\n' ) // Command is submitted
- {
- // Clear the command field
- if( clearCommandAfterExecution )
- commandInputField.text = "";
- if( text.Length > 0 )
- {
- if( commandHistory.Count == 0 || commandHistory[commandHistory.Count - 1] != text )
- commandHistory.Add( text );
- commandHistoryIndex = -1;
- // Execute the command
- DebugLogConsole.ExecuteCommand( text );
- // Snap to bottom and select the latest entry
- SetSnapToBottom( true );
- }
- return '\0';
- }
- return addedChar;
- }
- // A debug entry is received
- private void ReceivedLog( string logString, string stackTrace, LogType logType )
- {
- #if UNITY_EDITOR
- if( isQuittingApplication )
- return;
- #endif
- // Truncate the log if it is longer than maxLogLength
- int logLength = logString.Length;
- if( stackTrace == null )
- {
- if( logLength > maxLogLength )
- logString = logString.Substring( 0, maxLogLength - 11 ) + "<truncated>";
- }
- else
- {
- logLength += stackTrace.Length;
- if( logLength > maxLogLength )
- {
- // Decide which log component(s) to truncate
- int halfMaxLogLength = maxLogLength / 2;
- if( logString.Length >= halfMaxLogLength )
- {
- if( stackTrace.Length >= halfMaxLogLength )
- {
- // Truncate both logString and stackTrace
- logString = logString.Substring( 0, halfMaxLogLength - 11 ) + "<truncated>";
- // If stackTrace doesn't end with a blank line, its last line won't be visible in the console for some reason
- stackTrace = stackTrace.Substring( 0, halfMaxLogLength - 12 ) + "<truncated>\n";
- }
- else
- {
- // Truncate logString
- logString = logString.Substring( 0, maxLogLength - stackTrace.Length - 11 ) + "<truncated>";
- }
- }
- else
- {
- // Truncate stackTrace
- stackTrace = stackTrace.Substring( 0, maxLogLength - logString.Length - 12 ) + "<truncated>\n";
- }
- }
- }
- QueuedDebugLogEntry queuedLogEntry = new QueuedDebugLogEntry( logString, stackTrace, logType );
- lock( logEntriesLock )
- {
- queuedLogEntries.Add( queuedLogEntry );
- }
- }
- // Present the log entry in the console
- private void ProcessLog( QueuedDebugLogEntry queuedLogEntry )
- {
- LogType logType = queuedLogEntry.logType;
- DebugLogEntry logEntry;
- if( pooledLogEntries.Count > 0 )
- {
- logEntry = pooledLogEntries[pooledLogEntries.Count - 1];
- pooledLogEntries.RemoveAt( pooledLogEntries.Count - 1 );
- }
- else
- logEntry = new DebugLogEntry();
- logEntry.Initialize( queuedLogEntry.logString, queuedLogEntry.stackTrace );
- // Check if this entry is a duplicate (i.e. has been received before)
- int logEntryIndex;
- bool isEntryInCollapsedEntryList = collapsedLogEntriesMap.TryGetValue( logEntry, out logEntryIndex );
- if( !isEntryInCollapsedEntryList )
- {
- // It is not a duplicate,
- // add it to the list of unique debug entries
- logEntry.logTypeSpriteRepresentation = logSpriteRepresentations[logType];
- logEntryIndex = collapsedLogEntries.Count;
- collapsedLogEntries.Add( logEntry );
- collapsedLogEntriesMap[logEntry] = logEntryIndex;
- }
- else
- {
- // It is a duplicate, pool the duplicate log entry and
- // increment the original debug item's collapsed count
- pooledLogEntries.Add( logEntry );
- logEntry = collapsedLogEntries[logEntryIndex];
- logEntry.count++;
- }
- // Add the index of the unique debug entry to the list
- // that stores the order the debug entries are received
- uncollapsedLogEntriesIndices.Add( logEntryIndex );
- // If this debug entry matches the current filters,
- // add it to the list of debug entries to show
- Sprite logTypeSpriteRepresentation = logEntry.logTypeSpriteRepresentation;
- if( isCollapseOn && isEntryInCollapsedEntryList )
- {
- if( isLogWindowVisible )
- {
- if( !isInSearchMode && logFilter == DebugLogFilter.All )
- recycledListView.OnCollapsedLogEntryAtIndexUpdated( logEntryIndex );
- else
- recycledListView.OnCollapsedLogEntryAtIndexUpdated( indicesOfListEntriesToShow.IndexOf( logEntryIndex ) );
- }
- }
- else if( ( !isInSearchMode || queuedLogEntry.MatchesSearchTerm( searchTerm ) ) && ( logFilter == DebugLogFilter.All ||
- ( logTypeSpriteRepresentation == infoLog && ( ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info ) ) ||
- ( logTypeSpriteRepresentation == warningLog && ( ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning ) ) ||
- ( logTypeSpriteRepresentation == errorLog && ( ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error ) ) ) )
- {
- indicesOfListEntriesToShow.Add( logEntryIndex );
- if( isLogWindowVisible )
- recycledListView.OnLogEntriesUpdated( false );
- }
- if( logType == LogType.Log )
- {
- infoEntryCount++;
- infoEntryCountText.text = infoEntryCount.ToString();
- // If debug popup is visible, notify it of the new debug entry
- if( !isLogWindowVisible )
- popupManager.NewInfoLogArrived();
- }
- else if( logType == LogType.Warning )
- {
- warningEntryCount++;
- warningEntryCountText.text = warningEntryCount.ToString();
- // If debug popup is visible, notify it of the new debug entry
- if( !isLogWindowVisible )
- popupManager.NewWarningLogArrived();
- }
- else
- {
- errorEntryCount++;
- errorEntryCountText.text = errorEntryCount.ToString();
- // If debug popup is visible, notify it of the new debug entry
- if( !isLogWindowVisible )
- popupManager.NewErrorLogArrived();
- }
- }
- // Value of snapToBottom is changed (user scrolled the list manually)
- public void SetSnapToBottom( bool snapToBottom )
- {
- this.snapToBottom = snapToBottom;
- }
- // Make sure the scroll bar of the scroll rect is adjusted properly
- public void ValidateScrollPosition()
- {
- logItemsScrollRect.OnScroll( nullPointerEventData );
- }
- // Hide button is clicked
- public void HideButtonPressed()
- {
- ShowPopup();
- }
- // Clear button is clicked
- public void ClearButtonPressed()
- {
- snapToBottom = true;
- infoEntryCount = 0;
- warningEntryCount = 0;
- errorEntryCount = 0;
- infoEntryCountText.text = "0";
- warningEntryCountText.text = "0";
- errorEntryCountText.text = "0";
- collapsedLogEntries.Clear();
- collapsedLogEntriesMap.Clear();
- uncollapsedLogEntriesIndices.Clear();
- indicesOfListEntriesToShow.Clear();
- recycledListView.DeselectSelectedLogItem();
- recycledListView.OnLogEntriesUpdated( true );
- }
- // Collapse button is clicked
- public void CollapseButtonPressed()
- {
- // Swap the value of collapse mode
- isCollapseOn = !isCollapseOn;
- snapToBottom = true;
- collapseButton.color = isCollapseOn ? collapseButtonSelectedColor : collapseButtonNormalColor;
- recycledListView.SetCollapseMode( isCollapseOn );
- // Determine the new list of debug entries to show
- FilterLogs();
- }
- // Filtering mode of info logs has changed
- public void FilterLogButtonPressed()
- {
- logFilter = logFilter ^ DebugLogFilter.Info;
- if( ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info )
- filterInfoButton.color = filterButtonsSelectedColor;
- else
- filterInfoButton.color = filterButtonsNormalColor;
- FilterLogs();
- }
- // Filtering mode of warning logs has changed
- public void FilterWarningButtonPressed()
- {
- logFilter = logFilter ^ DebugLogFilter.Warning;
- if( ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning )
- filterWarningButton.color = filterButtonsSelectedColor;
- else
- filterWarningButton.color = filterButtonsNormalColor;
- FilterLogs();
- }
- // Filtering mode of error logs has changed
- public void FilterErrorButtonPressed()
- {
- logFilter = logFilter ^ DebugLogFilter.Error;
- if( ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error )
- filterErrorButton.color = filterButtonsSelectedColor;
- else
- filterErrorButton.color = filterButtonsNormalColor;
- FilterLogs();
- }
- // Search term has changed
- public void SearchTermChanged( string searchTerm )
- {
- if( searchTerm != null )
- searchTerm = searchTerm.Trim();
- this.searchTerm = searchTerm;
- bool isInSearchMode = !string.IsNullOrEmpty( searchTerm );
- if( isInSearchMode || this.isInSearchMode )
- {
- this.isInSearchMode = isInSearchMode;
- FilterLogs();
- }
- }
- // Debug window is being resized,
- // Set the sizeDelta property of the window accordingly while
- // preventing window dimensions from going below the minimum dimensions
- public void Resize( BaseEventData dat )
- {
- PointerEventData eventData = (PointerEventData) dat;
- // Grab the resize button from top; 36f is the height of the resize button
- float newHeight = ( eventData.position.y - logWindowTR.position.y ) / -canvasTR.localScale.y + 36f;
- if( newHeight < minimumHeight )
- newHeight = minimumHeight;
- Vector2 anchorMin = logWindowTR.anchorMin;
- anchorMin.y = Mathf.Max( 0f, 1f - newHeight / canvasTR.sizeDelta.y );
- logWindowTR.anchorMin = anchorMin;
- // Update the recycled list view
- recycledListView.OnViewportDimensionsChanged();
- }
- // Determine the filtered list of debug entries to show on screen
- private void FilterLogs()
- {
- indicesOfListEntriesToShow.Clear();
- if( logFilter != DebugLogFilter.None )
- {
- if( logFilter == DebugLogFilter.All )
- {
- if( isCollapseOn )
- {
- if( !isInSearchMode )
- {
- // All the unique debug entries will be listed just once.
- // So, list of debug entries to show is the same as the
- // order these unique debug entries are added to collapsedLogEntries
- for( int i = 0, count = collapsedLogEntries.Count; i < count; i++ )
- indicesOfListEntriesToShow.Add( i );
- }
- else
- {
- for( int i = 0, count = collapsedLogEntries.Count; i < count; i++ )
- {
- if( collapsedLogEntries[i].MatchesSearchTerm( searchTerm ) )
- indicesOfListEntriesToShow.Add( i );
- }
- }
- }
- else
- {
- if( !isInSearchMode )
- {
- for( int i = 0, count = uncollapsedLogEntriesIndices.Count; i < count; i++ )
- indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
- }
- else
- {
- for( int i = 0, count = uncollapsedLogEntriesIndices.Count; i < count; i++ )
- {
- if( collapsedLogEntries[uncollapsedLogEntriesIndices[i]].MatchesSearchTerm( searchTerm ) )
- indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
- }
- }
- }
- }
- else
- {
- // Show only the debug entries that match the current filter
- bool isInfoEnabled = ( logFilter & DebugLogFilter.Info ) == DebugLogFilter.Info;
- bool isWarningEnabled = ( logFilter & DebugLogFilter.Warning ) == DebugLogFilter.Warning;
- bool isErrorEnabled = ( logFilter & DebugLogFilter.Error ) == DebugLogFilter.Error;
- if( isCollapseOn )
- {
- for( int i = 0, count = collapsedLogEntries.Count; i < count; i++ )
- {
- DebugLogEntry logEntry = collapsedLogEntries[i];
- if( isInSearchMode && !logEntry.MatchesSearchTerm( searchTerm ) )
- continue;
- if( logEntry.logTypeSpriteRepresentation == infoLog )
- {
- if( isInfoEnabled )
- indicesOfListEntriesToShow.Add( i );
- }
- else if( logEntry.logTypeSpriteRepresentation == warningLog )
- {
- if( isWarningEnabled )
- indicesOfListEntriesToShow.Add( i );
- }
- else if( isErrorEnabled )
- indicesOfListEntriesToShow.Add( i );
- }
- }
- else
- {
- for( int i = 0, count = uncollapsedLogEntriesIndices.Count; i < count; i++ )
- {
- DebugLogEntry logEntry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
- if( isInSearchMode && !logEntry.MatchesSearchTerm( searchTerm ) )
- continue;
- if( logEntry.logTypeSpriteRepresentation == infoLog )
- {
- if( isInfoEnabled )
- indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
- }
- else if( logEntry.logTypeSpriteRepresentation == warningLog )
- {
- if( isWarningEnabled )
- indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
- }
- else if( isErrorEnabled )
- indicesOfListEntriesToShow.Add( uncollapsedLogEntriesIndices[i] );
- }
- }
- }
- }
- // Update the recycled list view
- recycledListView.DeselectSelectedLogItem();
- recycledListView.OnLogEntriesUpdated( true );
- ValidateScrollPosition();
- }
- public string GetAllLogs()
- {
- int count = uncollapsedLogEntriesIndices.Count;
- int length = 0;
- int newLineLength = System.Environment.NewLine.Length;
- for( int i = 0; i < count; i++ )
- {
- DebugLogEntry entry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
- length += entry.logString.Length + entry.stackTrace.Length + newLineLength * 3;
- }
- length += 100; // Just in case...
- System.Text.StringBuilder sb = new System.Text.StringBuilder( length );
- for( int i = 0; i < count; i++ )
- {
- DebugLogEntry entry = collapsedLogEntries[uncollapsedLogEntriesIndices[i]];
- sb.AppendLine( entry.logString ).AppendLine( entry.stackTrace ).AppendLine();
- }
- return sb.ToString();
- }
- private void SaveLogsToFile()
- {
- string path = Path.Combine( Application.persistentDataPath, System.DateTime.Now.ToString( "dd-MM-yyyy--HH-mm-ss" ) + ".txt" );
- File.WriteAllText( path, instance.GetAllLogs() );
- Debug.Log( "Logs saved to: " + path );
- }
- // If a cutout is intersecting with debug window on notch screens, shift the window downwards
- private void CheckScreenCutout()
- {
- if( !avoidScreenCutout )
- return;
- #if UNITY_2017_2_OR_NEWER && !UNITY_EDITOR && ( UNITY_ANDROID || UNITY_IOS )
- // Check if there is a cutout at the top of the screen
- int screenHeight = Screen.height;
- float safeYMax = Screen.safeArea.yMax;
- if( safeYMax < screenHeight - 1 ) // 1: a small threshold
- {
- // There is a cutout, shift the log window downwards
- float cutoutPercentage = ( screenHeight - safeYMax ) / Screen.height;
- float cutoutLocalSize = cutoutPercentage * canvasTR.rect.height;
- logWindowTR.anchoredPosition = new Vector2( 0f, -cutoutLocalSize );
- logWindowTR.sizeDelta = new Vector2( 0f, -cutoutLocalSize );
- }
- else
- {
- logWindowTR.anchoredPosition = Vector2.zero;
- logWindowTR.sizeDelta = Vector2.zero;
- }
- #endif
- }
- // Pool an unused log item
- public void PoolLogItem( DebugLogItem logItem )
- {
- logItem.gameObject.SetActive( false );
- pooledLogItems.Add( logItem );
- }
- // Fetch a log item from the pool
- public DebugLogItem PopLogItem()
- {
- DebugLogItem newLogItem;
- // If pool is not empty, fetch a log item from the pool,
- // create a new log item otherwise
- if( pooledLogItems.Count > 0 )
- {
- newLogItem = pooledLogItems[pooledLogItems.Count - 1];
- pooledLogItems.RemoveAt( pooledLogItems.Count - 1 );
- newLogItem.gameObject.SetActive( true );
- }
- else
- {
- newLogItem = (DebugLogItem) Instantiate( logItemPrefab, logItemsContainer, false );
- newLogItem.Initialize( recycledListView );
- }
- return newLogItem;
- }
- }
- }
|