using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using Explorer.UI.Main.Pages.Console; using ExplorerBeta; using ExplorerBeta.UI; using ExplorerBeta.UI.Main; using ExplorerBeta.Unstrip.Resources; using TMPro; using UnityEngine; using UnityEngine.UI; #if CPP using UnhollowerRuntimeLib; #endif namespace Explorer.UI.Main.Pages { // TODO: Maybe add interface for managing the Using directives of the console. // Otherwise it's pretty much done. public class ConsolePage : BaseMenuPage { public override string Name => "C# Console"; public static ConsolePage Instance { get; private set; } public static bool EnableSuggestions { get; set; } = true; public static bool EnableAutoIndent { get; set; } = true; public CodeEditor m_codeEditor; public ScriptEvaluator m_evaluator; public static List AutoCompletes = new List(); public static List UsingDirectives; public static readonly string[] DefaultUsing = new string[] { "System", "UnityEngine", "System.Linq", "System.Collections", "System.Collections.Generic", "System.Reflection" }; public override void Init() { Instance = this; try { ResetConsole(); foreach (var use in DefaultUsing) { AddUsing(use); } ConstructUI(); AutoCompleter.Init(); } catch (Exception e) { ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}"); // TODO } } public override void Update() { m_codeEditor?.Update(); AutoCompleter.Update(); } internal string AsmToUsing(string asm) => $"using {asm};"; public void AddUsing(string asm) { if (!UsingDirectives.Contains(asm)) { UsingDirectives.Add(asm); Evaluate(AsmToUsing(asm), true); } } public object Evaluate(string str, bool suppressWarning = false) { object ret = VoidType.Value; m_evaluator.Compile(str, out var compiled); try { if (compiled == null) { throw new Exception("Mono.Csharp Service was unable to compile the code provided."); } compiled.Invoke(ref ret); } catch (Exception e) { if (!suppressWarning) { ExplorerCore.LogWarning(e.GetType() + ", " + e.Message); } } return ret; } public void ResetConsole() { if (m_evaluator != null) { m_evaluator.Dispose(); } m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) }; UsingDirectives = new List(); } internal void OnInputChanged() { AutoCompleter.CheckAutocomplete(); AutoCompleter.SetSuggestions(AutoCompletes.ToArray()); } public void UseAutocomplete(string suggestion) { int cursorIndex = m_codeEditor.InputField.caretPosition; var input = m_codeEditor.InputField.text; input = input.Insert(cursorIndex, suggestion); m_codeEditor.InputField.text = input; m_codeEditor.InputField.caretPosition += suggestion.Length; AutoCompleter.ClearAutocompletes(); } #region UI Construction public void ConstructUI() { Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport); var mainLayout = Content.AddComponent(); mainLayout.preferredHeight = 300; mainLayout.flexibleHeight = 4; var mainGroup = Content.AddComponent(); mainGroup.childControlHeight = true; mainGroup.childControlWidth = true; mainGroup.childForceExpandHeight = true; mainGroup.childForceExpandWidth = true; #region TOP BAR // Main group object var topBarObj = UIFactory.CreateHorizontalGroup(Content); var topBarLayout = topBarObj.AddComponent(); topBarLayout.minHeight = 50; topBarLayout.flexibleHeight = 0; var topBarGroup = topBarObj.GetComponent(); topBarGroup.padding.left = 30; topBarGroup.padding.right = 30; topBarGroup.padding.top = 8; topBarGroup.padding.bottom = 8; topBarGroup.spacing = 10; topBarGroup.childForceExpandHeight = true; topBarGroup.childForceExpandWidth = true; topBarGroup.childControlWidth = true; topBarGroup.childControlHeight = true; topBarGroup.childAlignment = TextAnchor.LowerCenter; var topBarLabel = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft); var topBarLabelLayout = topBarLabel.AddComponent(); topBarLabelLayout.preferredWidth = 800; topBarLabelLayout.flexibleWidth = 10; var topBarText = topBarLabel.GetComponent(); topBarText.text = "C# Console"; topBarText.fontSize = 20; // Enable Suggestions toggle var suggestToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle suggestToggle, out Text suggestToggleText); #if CPP suggestToggle.onValueChanged.AddListener(new Action(SuggestToggleCallback)); #else suggestToggle.onValueChanged.AddListener(SuggestToggleCallback); #endif void SuggestToggleCallback(bool val) { EnableSuggestions = val; AutoCompleter.Update(); } suggestToggleText.text = "Suggestions"; suggestToggleText.alignment = TextAnchor.UpperLeft; var suggestTextPos = suggestToggleText.transform.localPosition; suggestTextPos.y = -14; suggestToggleText.transform.localPosition = suggestTextPos; var suggestLayout = suggestToggleObj.AddComponent(); suggestLayout.minWidth = 120; suggestLayout.flexibleWidth = 0; var suggestRect = suggestToggleObj.transform.Find("Background"); var suggestPos = suggestRect.localPosition; suggestPos.y = -14; suggestRect.localPosition = suggestPos; // Enable Auto-indent toggle var autoIndentToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle autoIndentToggle, out Text autoIndentToggleText); #if CPP autoIndentToggle.onValueChanged.AddListener(new Action((bool val) => { EnableAutoIndent = val; })); #else autoIndentToggle.onValueChanged.AddListener(OnIndentChanged); void OnIndentChanged(bool val) => EnableAutoIndent = val; #endif autoIndentToggleText.text = "Auto-indent"; autoIndentToggleText.alignment = TextAnchor.UpperLeft; var autoIndentTextPos = autoIndentToggleText.transform.localPosition; autoIndentTextPos.y = -14; autoIndentToggleText.transform.localPosition = autoIndentTextPos; var autoIndentLayout = autoIndentToggleObj.AddComponent(); autoIndentLayout.minWidth = 120; autoIndentLayout.flexibleWidth = 0; var autoIndentRect = autoIndentToggleObj.transform.Find("Background"); suggestPos = autoIndentRect.localPosition; suggestPos.y = -14; autoIndentRect.localPosition = suggestPos; #endregion #region CONSOLE INPUT var consoleBase = UIFactory.CreateUIObject("CodeEditor", Content); var consoleLayout = consoleBase.AddComponent(); consoleLayout.preferredHeight = 500; consoleLayout.flexibleHeight = 50; consoleBase.AddComponent(); var mainRect = consoleBase.GetComponent(); mainRect.pivot = Vector2.one * 0.5f; mainRect.anchorMin = Vector2.zero; mainRect.anchorMax = Vector2.one; mainRect.offsetMin = Vector2.zero; mainRect.offsetMax = Vector2.zero; var mainBg = UIFactory.CreateUIObject("MainBackground", consoleBase); var mainBgRect = mainBg.GetComponent(); mainBgRect.pivot = new Vector2(0, 1); mainBgRect.anchorMin = Vector2.zero; mainBgRect.anchorMax = Vector2.one; mainBgRect.offsetMin = Vector2.zero; mainBgRect.offsetMax = Vector2.zero; var mainBgImage = mainBg.AddComponent(); var lineHighlight = UIFactory.CreateUIObject("LineHighlight", consoleBase); var lineHighlightRect = lineHighlight.GetComponent(); lineHighlightRect.pivot = new Vector2(0.5f, 1); lineHighlightRect.anchorMin = new Vector2(0, 1); lineHighlightRect.anchorMax = new Vector2(1, 1); lineHighlightRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 21); var lineHighlightImage = lineHighlight.GetComponent(); if (!lineHighlightImage) lineHighlightImage = lineHighlight.AddComponent(); var linesBg = UIFactory.CreateUIObject("LinesBackground", consoleBase); var linesBgRect = linesBg.GetComponent(); linesBgRect.anchorMin = Vector2.zero; linesBgRect.anchorMax = new Vector2(0, 1); linesBgRect.offsetMin = new Vector2(-17.5f, 0); linesBgRect.offsetMax = new Vector2(17.5f, 0); linesBgRect.sizeDelta = new Vector2(65, 0); var linesBgImage = linesBg.AddComponent(); var inputObj = UIFactory.CreateTMPInput(consoleBase); var inputField = inputObj.GetComponent(); inputField.richText = false; var inputRect = inputObj.GetComponent(); inputRect.pivot = new Vector2(0.5f, 0.5f); inputRect.anchorMin = Vector2.zero; inputRect.anchorMax = new Vector2(0.92f, 1); inputRect.offsetMin = new Vector2(20, 0); inputRect.offsetMax = new Vector2(14, 0); inputRect.anchoredPosition = new Vector2(40, 0); var textAreaObj = inputObj.transform.Find("TextArea"); var textAreaRect = textAreaObj.GetComponent(); textAreaRect.pivot = new Vector2(0.5f, 0.5f); textAreaRect.anchorMin = Vector2.zero; textAreaRect.anchorMax = Vector2.one; var mainTextObj = textAreaObj.transform.Find("Text"); var mainTextRect = mainTextObj.GetComponent(); mainTextRect.pivot = new Vector2(0.5f, 0.5f); mainTextRect.anchorMin = Vector2.zero; mainTextRect.anchorMax = Vector2.one; mainTextRect.offsetMin = Vector2.zero; mainTextRect.offsetMax = Vector2.zero; var mainTextInput = mainTextObj.GetComponent(); mainTextInput.fontSize = 18; var placeHolderText = textAreaObj.transform.Find("Placeholder").GetComponent(); placeHolderText.text = @"Welcome to the Explorer C# Console! Use the Help(); command for a list of available helper methods, or Log(""[message]""); to print something to console."; var linesTextObj = UIFactory.CreateUIObject("LinesText", mainTextObj.gameObject); var linesTextRect = linesTextObj.GetComponent(); var linesTextInput = linesTextObj.AddComponent(); linesTextInput.fontSize = 18; var highlightTextObj = UIFactory.CreateUIObject("HighlightText", mainTextObj.gameObject); var highlightTextRect = highlightTextObj.GetComponent(); highlightTextRect.anchorMin = Vector2.zero; highlightTextRect.anchorMax = Vector2.one; highlightTextRect.offsetMin = Vector2.zero; highlightTextRect.offsetMax = Vector2.zero; var highlightTextInput = highlightTextObj.AddComponent(); highlightTextInput.fontSize = 18; var scroll = UIFactory.CreateScrollbar(consoleBase); var scrollRect = scroll.GetComponent(); scrollRect.anchorMin = new Vector2(1, 0); scrollRect.anchorMax = new Vector2(1, 1); scrollRect.pivot = new Vector2(0.5f, 1); scrollRect.offsetMin = new Vector2(-25f, 0); var scroller = scroll.GetComponent(); scroller.direction = Scrollbar.Direction.TopToBottom; var scrollColors = scroller.colors; scrollColors.normalColor = new Color(0.6f, 0.6f, 0.6f, 1.0f); scroller.colors = scrollColors; var scrollImage = scroll.GetComponent(); var tmpInput = inputObj.GetComponent(); tmpInput.scrollSensitivity = 15; tmpInput.verticalScrollbar = scroller; // set lines text anchors here after UI is fleshed out linesTextRect.pivot = Vector2.zero; linesTextRect.anchorMin = new Vector2(0, 0); linesTextRect.anchorMax = new Vector2(1, 1); linesTextRect.offsetMin = Vector2.zero; linesTextRect.offsetMax = Vector2.zero; linesTextRect.anchoredPosition = new Vector2(-40, 0); tmpInput.GetComponentInChildren().enabled = false; inputObj.GetComponent().enabled = false; #endregion #region COMPILE BUTTON var compileBtnObj = UIFactory.CreateButton(Content); var compileBtnLayout = compileBtnObj.AddComponent(); compileBtnLayout.preferredWidth = 80; compileBtnLayout.flexibleWidth = 0; compileBtnLayout.minHeight = 45; compileBtnLayout.flexibleHeight = 0; var compileButton = compileBtnObj.GetComponent