< Summary

Class:GDX.Developer.RuntimeConsoleController
Assembly:GDX
File(s):./Packages/com.dotbunny.gdx/GDX/Developer/RuntimeConsoleController.cs
Covered lines:0
Uncovered lines:202
Coverable lines:202
Total lines:365
Line coverage:0% (0 of 202)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:26
Method coverage:0% (0 of 26)

Coverage History

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
RuntimeConsoleController(...)0%20400%
UpdateFontSize(...)0%2100%
OnInputVerticalScroll(...)0%2100%
OnInputUp()0%12300%
OnInputDown()0%12300%
OnInputLeft()0%2100%
OnInputRight()0%2100%
OnInputSubmit()0%6200%
OnInputBackspace()0%6200%
OnInputAutocomplete()0%12300%
ClearSuggestion()0%2100%
ResetSuggestion()0%6200%
OnInputPageUp()0%2100%
OnInputPageDown()0%2100%
MakeItem()0%2100%
LogLevelToIcon(...)0%56700%
LogLevelToClass(...)0%56700%
BindItem(...)0%2100%
ClearInput()0%2100%
Tick()0%6200%
Show(...)0%2100%
OnTextInput(...)0%12300%

File(s)

./Packages/com.dotbunny.gdx/GDX/Developer/RuntimeConsoleController.cs

#LineLine coverage
 1// Copyright (c) 2020-2024 dotBunny Inc.
 2// dotBunny licenses this file to you under the BSL-1.0 license.
 3// See the LICENSE file in the project root for more information.
 4
 5using System;
 6using System.Globalization;
 7using System.Runtime.CompilerServices;
 8using System.Text;
 9using GDX.Developer.ConsoleCommands;
 10using GDX.RuntimeContent;
 11using UnityEngine;
 12#if GDX_INPUT
 13using UnityEngine.InputSystem;
 14#endif
 15using UnityEngine.UIElements;
 16using Object = UnityEngine.Object;
 17
 18namespace GDX.Developer
 19{
 20#if UNITY_2022_2_OR_NEWER
 21    public class RuntimeConsoleController
 22    {
 23        const string k_GameObjectName = "GDX_RuntimeConsole";
 24
 25        // Maybe expose these for people messing with fonts
 26        const int k_SourceFontSize = 14;
 27        const int k_FrameCountWidth = 100;
 28        const int k_SourceLevelWidth = 22;
 29
 30        readonly ListView m_ConsoleListView;
 31        readonly Label m_InputCaret;
 32        readonly ScrollView m_ConsoleScrollView;
 033        readonly StringBuilder m_InputBuilder = new StringBuilder(1000);
 34        readonly Label m_InputLabel;
 35        readonly Label m_SuggestionLabel;
 36        readonly VisualElement m_ConsoleBarElement;
 37
 038        int m_CommandBufferOffset = -1;
 39
 40        uint m_CurrentVersion;
 41        bool m_IsSubscribedToEvents;
 42        readonly VisualElement m_RootElement;
 43
 044        int m_FontSize = 14;
 045        float m_FontSizeMultiplier = 1f;
 46
 047        StyleLength m_FrameWidth = new StyleLength(new Length(k_FrameCountWidth, LengthUnit.Pixel));
 048        StyleLength m_LevelWidth = new StyleLength(new Length(k_SourceLevelWidth, LengthUnit.Pixel));
 49
 050        public UIDocument Document { get; private set; }
 051        public GameObject ConsoleGameObject { get; private set; }
 52
 053        public RuntimeConsoleController(GameObject parentGameObject, int initialFontSize)
 054        {
 55#if UNITY_EDITOR
 56
 57            // Helps with domain reload, removes the old object, and recreates
 058            if (parentGameObject.transform.childCount != 0)
 059            {
 060                int childCount = parentGameObject.transform.childCount;
 061                for (int i = 0; i < childCount; i++)
 062                {
 063                    if (parentGameObject.transform.GetChild(i).gameObject.name == k_GameObjectName)
 064                    {
 065                        Object.Destroy(parentGameObject.transform.GetChild(i).gameObject);
 066                        break;
 67                    }
 068                }
 069            }
 70#endif
 71
 72            // UIDocuments do not allow multiple components per Game Object so we have to make a child object.
 073            ConsoleGameObject = new GameObject(k_GameObjectName);
 074            ConsoleGameObject.transform.SetParent(parentGameObject.transform, false);
 75
 76            // Create isolated UI document  (thanks Damian, boy do I feel stupid.)
 077            Document = ConsoleGameObject.AddComponent<UIDocument>();
 078            Document.sortingOrder = float.MaxValue; // Above all
 079            Document.visualTreeAsset = ResourceProvider.GetUIElements().RuntimeConsole;
 80
 81            // Build out the required references
 082            m_RootElement = Document.rootVisualElement.Q<VisualElement>("gdx-console");
 083            m_ConsoleBarElement = m_RootElement.Q<VisualElement>("gdx-console-bar");
 084            m_InputLabel = m_ConsoleBarElement.Q<Label>("gdx-console-input");
 085            m_SuggestionLabel = m_ConsoleBarElement.Q<Label>("gdx-console-suggestion");
 086            m_InputCaret = m_ConsoleBarElement.Q<Label>("gdx-console-caret");
 87
 88            // We use a very slimmed down view of the consoles logs here as it takes time to process.
 089            m_ConsoleListView = m_RootElement.Q<ListView>("gdx-console-list");
 090            m_ConsoleScrollView = m_RootElement.Q<ScrollView>("");
 091            m_ConsoleListView.bindItem += BindItem;
 092            m_ConsoleListView.makeItem += MakeItem;
 093            m_ConsoleListView.itemsSource = Console.Log;
 94
 095            UpdateFontSize(initialFontSize);
 096        }
 97
 98        public void UpdateFontSize(int fontSize)
 099        {
 0100            m_FontSize = fontSize;
 0101            m_FontSizeMultiplier = (float)m_FontSize / k_SourceFontSize;
 102
 103            // Calculate our Category with based on the managed log longest
 0104            m_FrameWidth = new StyleLength(new Length(Mathf.RoundToInt(k_FrameCountWidth * m_FontSizeMultiplier)));
 0105            m_LevelWidth = new StyleLength(new Length(Mathf.RoundToInt(k_SourceLevelWidth * m_FontSizeMultiplier)));
 0106            m_ConsoleBarElement.style.height = fontSize + 10;
 0107            m_InputCaret.style.fontSize = fontSize;
 0108            m_InputLabel.style.fontSize = fontSize;
 0109            m_SuggestionLabel.style.fontSize = fontSize;
 110
 0111            m_ConsoleListView.Rebuild();
 0112        }
 113
 114        public void OnInputVerticalScroll(float delta)
 0115        {
 0116            m_ConsoleScrollView.Focus();
 0117            m_ConsoleScrollView.scrollOffset =
 118                new Vector2(
 119                    m_ConsoleScrollView.scrollOffset.x,
 120                    m_ConsoleScrollView.scrollOffset.y - delta * 10f);
 0121        }
 122
 123        public void OnInputUp()
 0124        {
 0125            int bufferCount = Console.PreviousCommandCount;
 0126            if (bufferCount <= 0)
 0127            {
 0128                return;
 129            }
 130
 0131            m_CommandBufferOffset++;
 0132            if (m_CommandBufferOffset >= bufferCount)
 0133            {
 0134                m_CommandBufferOffset = bufferCount - 1;
 0135            }
 136
 0137            m_InputBuilder.Clear();
 0138            m_InputBuilder.Append(Console.GetPreviousCommand(m_CommandBufferOffset));
 139
 140
 0141            m_InputLabel.text = m_InputBuilder.ToString();
 142
 0143            ClearSuggestion();
 0144        }
 145
 146        public void OnInputDown()
 0147        {
 148
 0149            int bufferCount = Console.PreviousCommandCount;
 0150            if (bufferCount <= 0)
 0151            {
 0152                return;
 153            }
 154
 0155            m_CommandBufferOffset--;
 0156            if (m_CommandBufferOffset < 0)
 0157            {
 0158                m_CommandBufferOffset = -1;
 0159                m_InputBuilder.Clear();
 0160            }
 161            else
 0162            {
 0163                m_InputBuilder.Clear();
 0164                m_InputBuilder.Append(Console.GetPreviousCommand(m_CommandBufferOffset));
 0165            }
 166
 0167            m_SuggestionLabel.text = string.Empty;
 0168            m_InputLabel.text = m_InputBuilder.ToString();
 0169        }
 170
 171
 172        public void OnInputLeft()
 0173        {
 0174        }
 175
 176        public void OnInputRight()
 0177        {
 178
 0179        }
 180
 181        public void OnInputSubmit()
 0182        {
 0183            if (m_SuggestionLabel.text != string.Empty)
 0184            {
 0185                m_InputBuilder.Append(m_SuggestionLabel.text);
 0186                m_InputLabel.text = m_InputBuilder.ToString();
 0187            }
 188            else
 0189            {
 0190                Console.QueueCommand(m_InputLabel.text);
 0191                m_CommandBufferOffset = -1;
 0192                m_InputLabel.text = "";
 0193                m_InputBuilder.Clear();
 0194                m_InputLabel.text = m_InputBuilder.ToString();
 0195            }
 0196            ClearSuggestion();
 0197        }
 198
 199        public void OnInputBackspace()
 0200        {
 0201            if (m_InputBuilder.Length >= 1)
 0202            {
 0203                m_InputBuilder.Remove(m_InputBuilder.Length - 1, 1);
 204
 0205                m_InputLabel.text = m_InputBuilder.ToString();
 206
 0207                ResetSuggestion();
 0208            }
 0209        }
 210
 211        public void OnInputAutocomplete()
 0212        {
 0213            m_SuggestionLabel.text = ConsoleAutoCompleteProvider.UpdateSuggestion(m_InputLabel.text)
 214                ? ConsoleAutoCompleteProvider.GetCurrentSuggestion()
 215                : string.Empty;
 0216        }
 217
 218        void ClearSuggestion()
 0219        {
 0220            m_SuggestionLabel.text = string.Empty;
 0221            ConsoleAutoCompleteProvider.Reset();
 0222        }
 223        void ResetSuggestion()
 0224        {
 0225            if (m_SuggestionLabel.text != string.Empty)
 0226            {
 0227                ConsoleAutoCompleteProvider.Reset();
 0228                OnInputAutocomplete();
 0229            }
 0230        }
 231
 232        public void OnInputPageUp()
 0233        {
 0234            m_ConsoleScrollView.Focus();
 0235            m_ConsoleScrollView.scrollOffset =
 236                new Vector2(
 237                    m_ConsoleScrollView.scrollOffset.x,
 238                    m_ConsoleScrollView.scrollOffset.y + (m_ConsoleScrollView.horizontalPageSize * 100));
 0239        }
 240
 241
 242        public void OnInputPageDown()
 0243        {
 0244            m_ConsoleScrollView.Focus();
 0245            m_ConsoleScrollView.scrollOffset =
 246                new Vector2(
 247                    m_ConsoleScrollView.scrollOffset.x,
 248                    m_ConsoleScrollView.scrollOffset.y - (m_ConsoleScrollView.horizontalPageSize * 100));
 0249        }
 250
 251        VisualElement MakeItem()
 0252        {
 0253            VisualElement itemBaseElement = new VisualElement { name = "gdx-console-item" };
 254
 0255            Label timestampLabel = new Label { name = "gdx-console-item-frame", style = { fontSize = m_FontSize, width =
 0256            Label levelLabel = new Label { name = "gdx-console-item-level" , style = { fontSize = m_FontSize, width = m_
 0257            Label messageLabel = new Label { name = "gdx-console-item-message", style = { fontSize = m_FontSize}, enable
 258
 0259            itemBaseElement.Add(timestampLabel);
 0260            itemBaseElement.Add(levelLabel);
 0261            itemBaseElement.Add(messageLabel);
 262
 0263            return itemBaseElement;
 0264        }
 265
 266        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 267        static char LogLevelToIcon(LogType level)
 0268        {
 0269            switch (level)
 270            {
 271                case LogType.Error:
 0272                    return '\uf06a';
 273                case LogType.Assert:
 0274                    return '\uf2d3';
 275                case LogType.Warning:
 0276                    return '\uf071';
 277                case LogType.Log:
 0278                    return '\uf27a';
 279                case LogType.Exception:
 0280                    return '\uf188';
 281            }
 0282            return '\uf27a';
 0283        }
 284        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 285        static string LogLevelToClass(LogType level)
 0286        {
 0287            switch (level)
 288            {
 289                case LogType.Error:
 0290                    return "error";
 291                case LogType.Assert:
 0292                    return "assert";
 293                case LogType.Warning:
 0294                    return "warning";
 295                case LogType.Log:
 0296                    return "log";
 297                case LogType.Exception:
 0298                    return "exception";
 299            }
 0300            return "default";
 0301        }
 302
 303        static void BindItem(VisualElement element, int index)
 0304        {
 0305            ConsoleLogEntry entry = Console.Log.GetEntryAt(index);
 0306            element.ClearClassList();
 0307            element.AddToClassList(LogLevelToClass(entry.Level));
 308
 0309            ((Label)element[0]).text = entry.FrameCount;
 310
 0311            ((Label)element[1]).text = LogLevelToIcon(entry.Level).ToString();
 0312            ((Label)element[1]).tooltip = entry.StackTrace;
 313
 0314            ((Label)element[2]).text = entry.Message;
 0315        }
 316
 317        public void ClearInput()
 0318        {
 0319            m_InputBuilder.Clear();
 0320            m_InputLabel.text = "";
 0321            ClearSuggestion();
 0322        }
 323
 324        public void Tick()
 0325        {
 0326            if (Console.Log.Version != m_CurrentVersion)
 0327            {
 0328                m_CurrentVersion = Console.Log.Version;
 0329                m_ConsoleListView.RefreshItems();
 0330                m_ConsoleListView.ScrollToItem(-1);
 0331            }
 0332        }
 333
 334        public void Show(Action delayedSubscribe)
 0335        {
 0336            m_InputLabel.schedule.Execute(delayedSubscribe);
 0337            Tick();
 338
 339            // This is bad, we have to scroll to fix things showing
 0340            m_InputLabel.schedule.Execute(() =>
 0341            {
 0342                m_ConsoleListView.ScrollToItem(-1);
 0343            });
 0344        }
 345
 346
 347        public void OnTextInput(char inputCharacter)
 0348        {
 349            // There are a few commands we can process for the console as they have defined text characters (in Ascii)
 0350            int asciiCode = inputCharacter;
 351
 352            // Outside the range of ASCII we want to support
 0353            if (asciiCode < 32 || asciiCode > 125)
 0354            {
 0355                return;
 356            }
 357
 0358            m_InputBuilder.Append(inputCharacter);
 0359            m_InputLabel.text = m_InputBuilder.ToString();
 360
 0361            ResetSuggestion();
 0362        }
 363    }
 364#endif // UNITY_2022_2_OR_NEWER
 365}