using QFSW.QC.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using TMPro; using UnityEngine; using UnityEngine.UI; namespace QFSW.QC { /// /// Provides the UI and I/O interface for the QuantumConsoleProcessor. Invokes commands on the processor and displays the output. /// [DisallowMultipleComponent] public class QuantumConsole : MonoBehaviour { /// /// Singleton reference to the console. Only valid and set if the singleton option is enabled for the console. /// public static QuantumConsole Instance { get; private set; } #pragma warning disable 0414, 0067, 0649 [SerializeField] private RectTransform _containerRect; [SerializeField] private ScrollRect _scrollRect; [SerializeField] private RectTransform _suggestionPopupRect; [SerializeField] private RectTransform _jobCounterRect; [SerializeField] private Image[] _panels; [SerializeField] private QuantumTheme _theme; [SerializeField] private QuantumKeyConfig _keyConfig; public QuantumTheme Theme => _theme; public QuantumKeyConfig KeyConfig => _keyConfig; [Command("verbose-errors", "If errors caused by the Quantum Console Processor or commands should be logged in verbose mode.", MonoTargetType.Registry)] [SerializeField] private bool _verboseErrors = false; [Command("verbose-logging", "The minimum log severity required to use verbose logging.", MonoTargetType.Registry)] [SerializeField] private LoggingThreshold _verboseLogging = LoggingThreshold.Never; [Command("logging-level", "The minimum log severity required to intercept and display the log.", MonoTargetType.Registry)] [SerializeField] private LoggingThreshold _loggingLevel = LoggingThreshold.Always; [SerializeField] private LoggingThreshold _openOnLogLevel = LoggingThreshold.Never; [SerializeField] private bool _interceptDebugLogger = true; [SerializeField] private bool _interceptWhilstInactive = true; [SerializeField] private bool _prependTimestamps = false; [SerializeField] private SupportedState _supportedState = SupportedState.Always; [SerializeField] private bool _activateOnStartup = true; [SerializeField] private bool _initialiseOnStartup = false; [SerializeField] private bool _closeOnSubmit = false; [SerializeField] private bool _singletonMode = false; [SerializeField] private AutoScrollOptions _autoScroll = AutoScrollOptions.OnInvoke; [SerializeField] private bool _showPopupDisplay = true; [SerializeField] private SortOrder _suggestionDisplayOrder = SortOrder.Descending; [SerializeField] private int _maxSuggestionDisplaySize = -1; [SerializeField] private bool _useFuzzySearch = false; [SerializeField] private bool _caseSensitiveSearch = true; [SerializeField] private bool _showCurrentJobs = true; [SerializeField] private bool _blockOnAsync = false; [SerializeField] private bool _storeCommandHistory = true; [SerializeField] private bool _storeDuplicateCommands = true; [SerializeField] private bool _storeAdjacentDuplicateCommands = false; [SerializeField] private int _commandHistorySize = -1; [SerializeField] private int _maxStoredLogs = 1024; [SerializeField] private int _maxLogSize = 8192; [SerializeField] private bool _showInitLogs = true; [SerializeField] private TMP_InputField _consoleInput; [SerializeField] private TextMeshProUGUI _inputPlaceholderText; [SerializeField] private TextMeshProUGUI _consoleLogText; [SerializeField] private TextMeshProUGUI _consoleSuggestionText; [SerializeField] private TextMeshProUGUI _suggestionPopupText; [SerializeField] private TextMeshProUGUI _jobCounterText; /// /// The maximum number of logs that may be stored in the log storage before old logs are removed. /// [Command("max-logs", MonoTargetType.Registry)] [CommandDescription("The maximum number of logs that may be stored in the log storage before old logs are removed.")] public int MaxStoredLogs { get => _maxStoredLogs; set { _maxStoredLogs = value; if (_logStorage != null) { _logStorage.MaxStoredLogs = value; } if (_logQueue != null) { _logQueue.MaxStoredLogs = value; } } } #pragma warning restore 0414, 0067, 0649 #region Callbacks /// Callback executed when the QC state changes. public event Action OnStateChange; /// Callback executed when the QC invokes a command. public event Action OnInvoke; /// Callback executed when the QC is cleared. public event Action OnClear; /// Callback executed when text has been logged to the QC. public event Action OnLog; /// Callback executed when the QC is activated. public event Action OnActivate; /// Callback executed when the QC is deactivated. public event Action OnDeactivate; /// Callback executed when the Command suggestion list is refreshed before presenting it to the user. public event Action> OnSuggestionsGenerated; #endregion private bool IsBlockedByAsync => _blockOnAsync && _currentTasks.Count > 0 || _currentActions.Count > 0; private readonly QuantumSerializer _serializer = new QuantumSerializer(); private ILogStorage _logStorage; private ILogQueue _logQueue; public bool IsActive { get; private set; } /// /// If any actions are currently executing /// public bool AreActionsExecuting => _currentActions.Count > 0; private readonly List _previousCommands = new List(); private readonly List _currentTasks = new List(); private readonly List> _currentActions = new List>(); private readonly List _suggestedCommands = new List(); private int _selectedPreviousCommandIndex = -1; private int _selectedSuggestionCommandIndex = -1; private string _currentText; private string _previousText; private bool _isGeneratingTable; private bool _consoleRequiresFlush; private TextMeshProUGUI[] _textComponents; private readonly Type _voidTaskType = typeof(Task<>).MakeGenericType(Type.GetType("System.Threading.Tasks.VoidTaskResult")); /// Applies a theme to the Quantum Console. /// The desired theme to apply. public void ApplyTheme(QuantumTheme theme, bool forceRefresh = false) { _theme = theme; if (theme) { if (_textComponents == null || forceRefresh) { _textComponents = GetComponentsInChildren(true); } foreach (TextMeshProUGUI text in _textComponents) { if (theme.Font) { text.font = theme.Font; } } foreach (Image panel in _panels) { panel.material = theme.PanelMaterial; panel.color = theme.PanelColor; } } } protected virtual void Update() { if (!IsActive) { if (_keyConfig.ShowConsoleKey.IsPressed() || _keyConfig.ToggleConsoleVisibilityKey.IsPressed()) { Activate(); } } else { ProcessAsyncTasks(); ProcessActions(); HandleAsyncJobCounter(); if (_keyConfig.HideConsoleKey.IsPressed() || _keyConfig.ToggleConsoleVisibilityKey.IsPressed()) { Deactivate(); return; } if (QuantumConsoleProcessor.TableIsGenerating) { _consoleInput.interactable = false; string consoleText = $"{_logStorage.GetLogString()}\n{GetTableGenerationText()}".Trim(); if (consoleText != _consoleLogText.text) { if (_showInitLogs) { OnStateChange?.Invoke(); _consoleLogText.text = consoleText; } if (_inputPlaceholderText) { _inputPlaceholderText.text = "Loading..."; } } return; } else if (IsBlockedByAsync) { OnStateChange?.Invoke(); _consoleInput.interactable = false; if (_inputPlaceholderText) { _inputPlaceholderText.text = "Executing async command..."; } } else if (!_consoleInput.interactable) { OnStateChange?.Invoke(); _consoleInput.interactable = true; if (_inputPlaceholderText) { _inputPlaceholderText.text = "Enter Command..."; } OverrideConsoleInput(string.Empty); if (_isGeneratingTable) { if (_showInitLogs) { AppendLog(new Log(GetTableGenerationText())); _consoleLogText.text = _logStorage.GetLogString(); } _isGeneratingTable = false; ScrollConsoleToLatest(); } } _previousText = _currentText; _currentText = _consoleInput.text; if (_currentText != _previousText) { OnTextChange(); } if (!IsBlockedByAsync) { if (InputHelper.GetKeyDown(_keyConfig.SubmitCommandKey)) { InvokeCommand(); } if (_storeCommandHistory) { ProcessCommandHistory(); } ProcessAutocomplete(); } } } private void LateUpdate() { if (IsActive) { FlushQueuedLogs(); FlushToConsoleText(); } } private string GetTableGenerationText() { string text = $"Q:\\>Quantum Console Processor is initialising"; text += $"\nQ:\\>Table generation under progress"; text += $"\nQ:\\>{QuantumConsoleProcessor.LoadedCommandCount} commands have been loaded"; if (QuantumConsoleProcessor.TableIsGenerating) { text += "..."; } else { text += ColorExtensions.ColorText($"\nQ:\\>Quantum Console Processor ready", _theme ? _theme.SuccessColor : Color.white); } return text; } private void ProcessCommandHistory() { if (InputHelper.GetKeyDown(_keyConfig.NextCommandKey) || InputHelper.GetKeyDown(_keyConfig.PreviousCommandKey)) { if (InputHelper.GetKeyDown(_keyConfig.NextCommandKey)) { _selectedPreviousCommandIndex++; } else if (_selectedPreviousCommandIndex > 0) { _selectedPreviousCommandIndex--; } _selectedPreviousCommandIndex = Mathf.Clamp(_selectedPreviousCommandIndex, -1, _previousCommands.Count - 1); if (_selectedPreviousCommandIndex > -1) { string command = _previousCommands[_previousCommands.Count - _selectedPreviousCommandIndex - 1]; OverrideConsoleInput(command); } } } private void GetCommandSuggestions() { _suggestedCommands.Clear(); RefreshCommandSuggestions(_suggestedCommands); OnSuggestionsGenerated?.Invoke(_suggestedCommands); } /// /// Overwrite to add cached commands to QuantumConsoleProcessor. /// protected virtual void RefreshCommandSuggestions(List suggestedCommands) { suggestedCommands.AddRange(QuantumConsoleProcessor.GetCommandSuggestions(_currentText, _useFuzzySearch, _caseSensitiveSearch, true)); } private void ProcessAutocomplete() { if ((_keyConfig.SuggestNextCommandKey.IsPressed() || _keyConfig.SuggestPreviousCommandKey.IsPressed()) && !string.IsNullOrWhiteSpace(_currentText)) { if (_selectedSuggestionCommandIndex < 0) { _selectedSuggestionCommandIndex = -1; GetCommandSuggestions(); } if (_suggestedCommands.Count > 0) { if (_keyConfig.SuggestPreviousCommandKey.IsPressed()) { _selectedSuggestionCommandIndex--; } else if (_keyConfig.SuggestNextCommandKey.IsPressed()) { _selectedSuggestionCommandIndex++; } _selectedSuggestionCommandIndex += _suggestedCommands.Count; _selectedSuggestionCommandIndex %= _suggestedCommands.Count; SetCommandSuggestion(_selectedSuggestionCommandIndex); } } } private string FormatSuggestion(CommandData command, bool selected) { if (!_theme) { return command.CommandSignature; } Color nameColor = Color.white; Color signatureColor = _theme.SuggestionColor; if (selected) { nameColor *= _theme.SelectedSuggestionColor; signatureColor *= _theme.SelectedSuggestionColor; } string nameSignature = command.CommandName.ColorText(nameColor); string genericSignature = command.GenericSignature; string paramSignature = command.ParameterSignature; return $"{nameSignature}{genericSignature} {paramSignature}".ColorText(signatureColor); } private void ProcessPopupDisplay() { if (string.IsNullOrWhiteSpace(_currentText)) { ClearPopup(); } else { if (_selectedSuggestionCommandIndex < 0) { GetCommandSuggestions(); } if (_suggestedCommands.Count == 0) { ClearPopup(); } else { if (_suggestionPopupRect && _suggestionPopupText) { int displaySize = _suggestedCommands.Count; if (_maxSuggestionDisplaySize > 0) { displaySize = Mathf.Min(displaySize, _maxSuggestionDisplaySize + 1); } IEnumerable suggestions = GetFormattedCommandSuggestions(displaySize); if (_suggestionDisplayOrder == SortOrder.Ascending) { suggestions = suggestions.Reverse(); } _suggestionPopupRect.gameObject.SetActive(true); _suggestionPopupText.text = string.Join("\n", suggestions); } } } } private IEnumerable GetFormattedCommandSuggestions(int displaySize) { for (int i = 0; i < displaySize; i++) { if (_maxSuggestionDisplaySize > 0 && i >= _maxSuggestionDisplaySize) { const string remainingSuggestion = "..."; if (_theme && _selectedSuggestionCommandIndex >= _maxSuggestionDisplaySize) { yield return remainingSuggestion.ColorText(_theme.SelectedSuggestionColor); } else { yield return remainingSuggestion; } } else { bool selected = i == _selectedSuggestionCommandIndex; string suggestion = FormatSuggestion(_suggestedCommands[i], selected); yield return $"{suggestion}"; } } } /// /// Sets the suggested command on the console. /// /// The index of the suggestion to set. public void SetCommandSuggestion(int suggestionIndex) { if (suggestionIndex < 0 || suggestionIndex > _suggestedCommands.Count) { throw new ArgumentException($"Cannot set suggestion to index {suggestionIndex}."); } _selectedSuggestionCommandIndex = suggestionIndex; SetCommandSuggestion(_suggestedCommands[_selectedSuggestionCommandIndex]); } private void SetCommandSuggestion(CommandData command) { OverrideConsoleInput(command.CommandName); Color suggestionColor = _theme ? _theme.SuggestionColor : Color.gray; _consoleSuggestionText.text = $"{command.CommandName.ColorText(Color.clear)}{command.GenericSignature.ColorText(suggestionColor)} {command.ParameterSignature.ColorText(suggestionColor)}"; } /// /// Overrides the console input field. /// /// The text to override the current input with. /// If the input field should be automatically focused. public void OverrideConsoleInput(string newInput, bool shouldFocus = true) { _currentText = newInput; _previousText = newInput; _consoleInput.text = newInput; if (shouldFocus) { FocusConsoleInput(); } OnTextChange(); } /// /// Selects and focuses the input field for the console. /// public void FocusConsoleInput() { _consoleInput.Select(); _consoleInput.caretPosition = _consoleInput.text.Length; _consoleInput.selectionAnchorPosition = _consoleInput.text.Length; _consoleInput.MoveTextEnd(false); _consoleInput.ActivateInputField(); } private void OnTextChange() { if (_selectedPreviousCommandIndex >= 0 && _currentText.Trim() != _previousCommands[_previousCommands.Count - _selectedPreviousCommandIndex - 1]) { ClearHistoricalSuggestions(); } if (_selectedSuggestionCommandIndex >= 0 && _currentText.Trim() != _suggestedCommands[_selectedSuggestionCommandIndex].CommandName) { ClearSuggestions(); } if (_showPopupDisplay) { ProcessPopupDisplay(); } } private void ClearHistoricalSuggestions() { _selectedPreviousCommandIndex = -1; } private void ClearSuggestions() { _selectedSuggestionCommandIndex = -1; _consoleSuggestionText.text = string.Empty; } private void ClearPopup() { if (_suggestionPopupRect) { _suggestionPopupRect.gameObject.SetActive(false); } if (_suggestionPopupText) { _suggestionPopupText.text = string.Empty; } } /// /// Invokes the command currently inputted into the Quantum Console. /// public void InvokeCommand() { if (!string.IsNullOrWhiteSpace(_consoleInput.text)) { string command = _consoleInput.text.Trim(); InvokeCommand(command); OverrideConsoleInput(string.Empty); StoreCommand(command); } } /// /// Invokes the given command. /// /// The command to invoke. /// The return value, if any, of the invoked command. public object InvokeCommand(string command) { object commandResult = null; if (!string.IsNullOrWhiteSpace(command)) { string commandLog = $"> {command}"; if (_theme) { commandLog = commandLog.ColorText(_theme.CommandLogColor); } LogToConsole(commandLog); string logTrace = string.Empty; try { commandResult = QuantumConsoleProcessor.InvokeCommand(command); switch (commandResult) { case Task task: _currentTasks.Add(task); break; case IEnumerator action: StartAction(action); break; case IEnumerable action: StartAction(action.GetEnumerator()); break; default: logTrace = Serialize(commandResult); break; } } catch (System.Reflection.TargetInvocationException e) { logTrace = GetInvocationErrorMessage(e.InnerException); } catch (Exception e) { logTrace = GetErrorMessage(e); } LogToConsole(logTrace); OnInvoke?.Invoke(command); if (_autoScroll == AutoScrollOptions.OnInvoke) { ScrollConsoleToLatest(); } if (_closeOnSubmit) { Deactivate(); } } else { OverrideConsoleInput(string.Empty); } return commandResult; } [Command("qc-script-extern", "Executes an external source of QC script file, where each line is a separate QC command.", MonoTargetType.Registry, Platform.AllPlatforms ^ Platform.WebGLPlayer)] public async Task InvokeExternalCommandsAsync(string filePath) { using (StreamReader reader = new StreamReader(filePath)) { while (!reader.EndOfStream) { string command = await reader.ReadLineAsync(); if (InvokeCommand(command) is Task ret) { await ret; ProcessAsyncTasks(); } } } } /// /// Invokes a sequence of commands, only starting a new command when the previous is complete. /// /// The commands to invoke. public async Task InvokeCommandsAsync(IEnumerable commands) { foreach (string command in commands) { if (InvokeCommand(command) is Task ret) { await ret; ProcessAsyncTasks(); } } } private string GetErrorMessage(Exception e) { string message = _verboseErrors ? $"Quantum Processor Error ({e.GetType()}): {e.Message}\n{e.StackTrace}" : $"Quantum Processor Error: {e.Message}"; return _theme ? message.ColorText(_theme.ErrorColor) : message; } private string GetInvocationErrorMessage(Exception e) { string message = _verboseErrors ? $"Error ({e.GetType()}): {e.Message}\n{e.StackTrace}" : $"Error: {e.Message}"; return _theme ? message.ColorText(_theme.ErrorColor) : message; } /// Thread safe API to format and log text to the Quantum Console. /// Text to be logged. /// The type of the log to be logged. public void LogToConsoleAsync(string logText, LogType logType = LogType.Log) { if (!string.IsNullOrWhiteSpace(logText)) { Log log = new Log(logText, logType); LogToConsoleAsync(log); } } /// Thread safe API to format and log text to the Quantum Console. /// Log to be logged. public void LogToConsoleAsync(ILog log) { OnLog?.Invoke(log); _logQueue.QueueLog(log); } private void FlushQueuedLogs() { bool scroll = false; bool open = false; while (_logQueue.TryDequeue(out ILog log)) { AppendLog(log); LoggingThreshold severity = log.Type.ToLoggingThreshold(); scroll |= _autoScroll == AutoScrollOptions.Always; open |= severity <= _openOnLogLevel; } if (scroll) { ScrollConsoleToLatest(); } if (open) { Activate(false); } } private void ProcessAsyncTasks() { for (int i = _currentTasks.Count - 1; i >= 0; i--) { if (_currentTasks[i].IsCompleted) { if (_currentTasks[i].IsFaulted) { foreach (Exception e in _currentTasks[i].Exception.InnerExceptions) { string error = GetInvocationErrorMessage(e); LogToConsole(error); } } else { Type taskType = _currentTasks[i].GetType(); if (taskType.IsGenericTypeOf(typeof(Task<>)) && !_voidTaskType.IsAssignableFrom(taskType)) { System.Reflection.PropertyInfo resultProperty = _currentTasks[i].GetType().GetProperty("Result"); object result = resultProperty.GetValue(_currentTasks[i]); string log = _serializer.SerializeFormatted(result, _theme); LogToConsole(log); } } _currentTasks.RemoveAt(i); } } } /// /// Starts executing an action. /// /// The action to start. public void StartAction(IEnumerator action) { _currentActions.Add(action); ProcessActions(); } /// /// Cancels any actions currently executing. /// public void CancelAllActions() { _currentActions.Clear(); } private void ProcessActions() { if (_keyConfig.CancelActionsKey.IsPressed()) { CancelAllActions(); return; } ActionContext context = new ActionContext { Console = this }; for (int i = _currentActions.Count - 1; i >= 0; i--) { IEnumerator action = _currentActions[i]; try { if (action.Execute(context) != ActionState.Running) { _currentActions.RemoveAt(i); } } catch (Exception e) { _currentActions.RemoveAt(i); string error = GetInvocationErrorMessage(e); LogToConsole(error); break; } } } private void HandleAsyncJobCounter() { if (_showCurrentJobs) { if (_jobCounterRect && _jobCounterText) { if (_currentTasks.Count == 0) { _jobCounterRect.gameObject.SetActive(false); } else { _jobCounterRect.gameObject.SetActive(true); _jobCounterText.text = $"{_currentTasks.Count} job{(_currentTasks.Count == 1 ? "" : "s")} in progress"; } } } } /// /// Serializes a value using the current serializer and theme. /// /// The value to the serialize. /// The serialized value. public string Serialize(object value) { return _serializer.SerializeFormatted(value, _theme); } /// /// Logs text to the Quantum Console. /// /// Text to be logged. /// If a newline should be ins public void LogToConsole(string logText, bool newLine = true) { bool logExists = !string.IsNullOrEmpty(logText); if (logExists) { LogToConsole(new Log(logText, LogType.Log, newLine)); } } /// /// Logs text to the Quantum Console. /// /// Log to be logged. public void LogToConsole(ILog log) { FlushQueuedLogs(); AppendLog(log); OnLog?.Invoke(log); if (_autoScroll == AutoScrollOptions.Always) { ScrollConsoleToLatest(); } } private void FlushToConsoleText() { if (_consoleRequiresFlush) { _consoleRequiresFlush = false; _consoleLogText.text = _logStorage.GetLogString(); } } private ILog TruncateLog(ILog log) { if (log.Text.Length <= _maxLogSize && _maxLogSize >= 0) return log; string msg = $"Log of size {log.Text.Length} exceeded the maximum log size of {_maxLogSize}"; if (_theme) { msg = msg.ColorText(_theme.ErrorColor); } return new Log(msg, LogType.Error); } protected void AppendLog(ILog log) { _logStorage.AddLog(TruncateLog(log)); RequireFlush(); } protected void RequireFlush() { _consoleRequiresFlush = true; } /// /// Removes the last log from the console. /// public void RemoveLogTrace() { _logStorage.RemoveLog(); RequireFlush(); } private void ScrollConsoleToLatest() { if (_scrollRect) { _scrollRect.verticalNormalizedPosition = 0; } } private void StoreCommand(string command) { if (_storeCommandHistory) { if (!_storeDuplicateCommands) { _previousCommands.Remove(command); } if (_storeAdjacentDuplicateCommands || _previousCommands.Count == 0 || _previousCommands[_previousCommands.Count - 1] != command) { _previousCommands.Add(command); } if (_commandHistorySize > 0 && _previousCommands.Count > _commandHistorySize) { _previousCommands.RemoveAt(0); } } } /// /// Clears the Quantum Console. /// [Command("clear", "Clears the Quantum Console", MonoTargetType.Registry)] public void ClearConsole() { _logStorage.Clear(); _logQueue.Clear(); _consoleLogText.text = string.Empty; _consoleLogText.SetLayoutDirty(); ClearBuffers(); OnClear?.Invoke(); } public string GetConsoleText() { return _consoleLogText.text; } protected virtual void ClearBuffers() { ClearHistoricalSuggestions(); ClearSuggestions(); ClearPopup(); } private void Awake() { InitializeLogging(); } private void OnEnable() { QuantumRegistry.RegisterObject(this); Application.logMessageReceivedThreaded += DebugIntercept; if (IsSupportedState()) { if (_singletonMode) { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else if (Instance != this) { Destroy(gameObject); } } if (_activateOnStartup) { bool shouldFocus = SystemInfo.deviceType == DeviceType.Desktop; Activate(shouldFocus); } else { if (_initialiseOnStartup) { Initialize(); } Deactivate(); } } else { DisableQC(); } } private bool IsSupportedState() { #if QC_DISABLED return false; #endif SupportedState currentState = SupportedState.Always; #if DEVELOPMENT_BUILD currentState = SupportedState.Development; #elif UNITY_EDITOR currentState = SupportedState.Editor; #endif return _supportedState <= currentState; } private void OnDisable() { QuantumRegistry.DeregisterObject(this); Application.logMessageReceivedThreaded -= DebugIntercept; Deactivate(); } private void DisableQC() { Deactivate(); enabled = false; } private void Initialize() { if (!QuantumConsoleProcessor.TableGenerated) { QuantumConsoleProcessor.GenerateCommandTable(true); _consoleInput.interactable = false; _isGeneratingTable = true; } InitializeLogging(); _consoleLogText.richText = true; _consoleSuggestionText.richText = true; ApplyTheme(_theme); if (!_keyConfig) { _keyConfig = ScriptableObject.CreateInstance(); } } private void InitializeLogging() { _logStorage = _logStorage ?? CreateLogStorage(); _logQueue = _logQueue ?? CreateLogQueue(); } protected virtual ILogStorage CreateLogStorage() => new LogStorage(_maxStoredLogs); protected virtual ILogQueue CreateLogQueue() => new LogQueue(_maxStoredLogs); /// /// Toggles the Quantum Console. /// public void Toggle() { if (IsActive) { Deactivate(); } else { Activate(); } } /// /// Activates the Quantum Console. /// /// If the input field should be automatically focused. public void Activate(bool shouldFocus = true) { Initialize(); IsActive = true; _containerRect.gameObject.SetActive(true); OverrideConsoleInput(string.Empty, shouldFocus); OnActivate?.Invoke(); } /// /// Deactivates the Quantum Console. /// public void Deactivate() { IsActive = false; _containerRect.gameObject.SetActive(false); OnDeactivate?.Invoke(); } private void DebugIntercept(string condition, string stackTrace, LogType type) { if (_interceptDebugLogger && (IsActive || _interceptWhilstInactive) && _loggingLevel >= type.ToLoggingThreshold()) { bool appendStackTrace = _verboseLogging >= type.ToLoggingThreshold(); ILog log = ConstructDebugLog(condition, stackTrace, type, _prependTimestamps, appendStackTrace); LogToConsoleAsync(log); } } protected virtual ILog ConstructDebugLog(string condition, string stackTrace, LogType type, bool prependTimeStamp, bool appendStackTrace) { if (prependTimeStamp) { DateTime now = DateTime.Now; string format = _theme ? _theme.TimestampFormat : "[{0:00}:{1:00}:{2:00}]"; condition = $"{string.Format(format, now.Hour, now.Minute, now.Second)} {condition}"; } if (appendStackTrace) { condition += $"\n{stackTrace}"; } if (_theme) { switch (type) { case LogType.Warning: { condition = ColorExtensions.ColorText(condition, _theme.WarningColor); break; } case LogType.Error: case LogType.Assert: case LogType.Exception: { condition = ColorExtensions.ColorText(condition, _theme.ErrorColor); break; } } } return new Log(condition, type, true); } protected virtual void OnValidate() { MaxStoredLogs = _maxStoredLogs; } } }