Advertisement
mvaganov

CmdLine.cs

May 1st, 2017
500
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 61.25 KB | None | 0 0
  1. #define CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  2. //#define UNKNOWN_CMDLINE_APPEARS
  3. using UnityEngine;
  4. using UnityEngine.SceneManagement;
  5. using UnityEngine.EventSystems;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Text;
  9. using UnityEngine.UI;
  10. using TMPro;
  11.  
  12. /// <summary>A Command Line emulation for Unity3D
  13. /// <description>Public Domain - This code is free, don't bother me about it!</description>
  14. /// <author email="mvaganov@hotmail.com">Michael Vaganov</author>
  15. public class CmdLine : MonoBehaviour {
  16.     #region commands
  17.     /// <summary>watching for commands *about to execute*.</summary>
  18.     public event CommandHandler OnCommand;
  19.     /// <summary>known commands</summary>
  20.     private Dictionary<string, Command> commands = new Dictionary<string, Command>();
  21.     /// <summary>every command can be executed by a different user</summary>
  22.     [Serializable]
  23.     public class Instruction {
  24.         public string text; public object user;
  25.         public bool IsUser(object a_user) { return user == a_user; }
  26.     }
  27.     /// <summary>queue of instructions that this command line needs to execute.</summary>
  28.     private List<Instruction> instructionList = new List<Instruction>();
  29.     private Instruction PopInstruction() {
  30.         if(instructionList.Count > 0) {
  31.             RecentInstruction = instructionList[0];
  32.             instructionList.RemoveAt(0);
  33.             return RecentInstruction;
  34.         }
  35.         return null;
  36.     }
  37.     [Tooltip("Easily accessible way of finding out what instruction was executed last")]
  38.     /// <summary>useful for callbacks, for finding out what is going on right now</summary>
  39.     public Instruction RecentInstruction;
  40.     /// <summary>the user object that should be used for normal input into this CmdLine</summary>
  41.     public object UserRawInput { get { return _tmpInputField; } }
  42.  
  43.     /// <summary>example of how to populate the command-line with commands</summary>
  44.     public void PopulateWithBasicCommands() {
  45.         //When adding commands, you must add a call below to registerCommand() with its name, implementation method, and help text.
  46.         addCommand("help", (args, user) => {
  47.             log(" - - - -\n" + CommandHelpString() + "\n - - - -");
  48.         }, "prints this help.");
  49.         addCommand("load", (args, user) => {
  50.             if(args.Length > 1) {
  51.                 if(args[1] == ".") { args[1] = SceneManager.GetActiveScene().name; }
  52.                 SceneManager.LoadScene(args[1]);
  53.             } else {
  54.                 log("to reload current scene, type <#" + ColorSet.SpecialTextHex + ">load " +
  55.                     SceneManager.GetActiveScene().name + "</color>");
  56.             }
  57.         }, "loads given scene. use: load <noparse><scene name></noparse>");
  58.         addCommand("pref", (args, user) => {
  59.             for(int i = 1; i < args.Length; ++i) {
  60.                 string output = null;
  61.                 try { output = "<#" + ColorSet.SpecialTextHex + ">" + PlayerPrefs.GetString(args[i]) + "</color>"; } catch(System.Exception e) { output = "<#" + ColorSet.ErrorTextHex + ">" + e.ToString() + "</color>"; }
  62.                 if(output == null) { output = "<#" + ColorSet.ErrorTextHex + ">null</color>"; }
  63.                 log(args[i] + ":" + output);
  64.             }
  65.         }, "shows player prefs value. use: pref [variableName, ...]");
  66.         addCommand("prefsave", (args, user) => {
  67.             if(args.Length > 1) {
  68.                 PlayerPrefs.SetString(args[1], (args.Length > 2) ? args[2] : null);
  69.                 PlayerPrefs.Save();
  70.             } else {
  71.                 log("missing arguments");
  72.             }
  73.         }, "save player prefs value. use: pref variableName [variableValue]");
  74.         addCommand("prefreset", (args, user) => {
  75.             PlayerPrefs.DeleteAll();
  76.             PlayerPrefs.Save();
  77.         }, "clears player prefs.");
  78.         addCommand("echo", (args, user) => {
  79.             println(string.Join(" ", args, 1, args.Length - 1));
  80.         }, "repeat given arguments as output");
  81.         addCommand("exit", (args, user) => {
  82. #if UNITY_EDITOR
  83.             UnityEditor.EditorApplication.isPlaying = false;
  84. #elif UNITY_WEBPLAYER
  85.                 Application.OpenURL(webplayerQuitURL);
  86. #else
  87.                 Application.Quit();
  88. #endif
  89.         }, "quits this application");
  90.         #region os_commandline_terminal
  91. #if CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  92.         addCommand("cmd", (args, user) => {
  93.             if(AllowSystemAccess) {
  94.                 bash.DoCommand(string.Join(" ", args, 1, args.Length - 1), this, null, this);
  95.             } else {
  96.                 HandleLog("Access Denied", "", LogType.Warning);
  97.             }
  98.         }, "access the true system's command-line terminal");
  99. #endif
  100.     }
  101.     public static void DoSystemCommand(string command, object whosAsking = null) {
  102.         bool isNewInstance = _instance == null;
  103.         Instance.doSystemCommand(command, whosAsking);
  104.         if(isNewInstance) { Instance.Interactivity = InteractivityEnum.Disabled; }
  105.     }
  106. #if !CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  107.     public void doSystemCommand(string command, object whosAsking = null) {
  108.         Debug.LogWarning(whosAsking+" can't do system command '"+command+
  109.             "', #define CONNECT_TO_REAL_COMMAND_LINE_TERMINAL");
  110.     }
  111. #else
  112.     public void doSystemCommand(string command, object whosAsking = null) {
  113.         bash.DoCommand(command, (whosAsking == null) ? bash : whosAsking, null, this);
  114.     }
  115.  
  116.     private class BASH {
  117.         System.Diagnostics.Process system_process;
  118.         System.Threading.Thread thread;
  119.         private string activeDir = null;
  120.         private string currentCommand = "";
  121.         private DoAfterStringIsRead currentCommand_callback;
  122.         /// the outputs from the bash thread
  123.         private List<string> log, err;
  124.         private bool isInitialized = false;
  125.         /// used to communicate to the CmdLine that the bash thread needs to refresh the prompt
  126.         private bool promptNeedsRedraw = false;
  127.         /// used to communicate to the CmdLine that the bash thread finished something
  128.         private bool probablyFinishedCommand = true;
  129.  
  130.         public void DoCommand(string s, object whosAsking, DoAfterStringIsRead cb = null, CmdLine cmd = null) {
  131.             if(thread == null) {
  132.                 currentCommand = s.Trim();
  133.                 currentCommand_callback = cb;
  134.                 log = new List<string>();
  135.                 err = new List<string>();
  136.                 thread = new System.Threading.Thread(delegate () {
  137. #if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || PLATFORM_STANDALONE_WIN
  138.                     activeDir = ".";
  139.                     string commandName = "cmd";
  140. #else
  141.                     activeDir = PWD();
  142.                     string commandName = "/bin/bash";
  143. #endif
  144.                     system_process = new System.Diagnostics.Process {
  145.                         StartInfo = new System.Diagnostics.ProcessStartInfo {
  146.                             FileName = commandName,
  147.                             Arguments = "",
  148.                             UseShellExecute = false,
  149.                             RedirectStandardOutput = true,
  150.                             RedirectStandardInput = true,
  151.                             RedirectStandardError = true,
  152.                             CreateNoWindow = true,
  153.                             WorkingDirectory = activeDir
  154.                         }
  155.                     };
  156.                     system_process.Start();
  157.                     bool ignoreOutput = true;
  158.                     system_process.OutputDataReceived += delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) {
  159.                         if(ignoreOutput) { return; }
  160.                         if(currentCommand_callback == null) {
  161.                             log.Add(e.Data);
  162.                             probablyFinishedCommand = true;
  163.                         } else {
  164.                             currentCommand_callback(e.Data);
  165.                             currentCommand_callback = null;
  166.                         }
  167.                     };
  168.                     system_process.BeginOutputReadLine();
  169.                     bool ignoreErrors = true;
  170.                     system_process.ErrorDataReceived += delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) {
  171.                         if(ignoreErrors) { return; }
  172.                         err.Add(e.Data);
  173.                         probablyFinishedCommand = true;
  174.                     };
  175.                     system_process.BeginErrorReadLine();
  176.                     system_process.StandardInput.WriteLine(' '); // force an error, because the StandardInput has a weird character in it to start with
  177.                     system_process.StandardInput.Flush();
  178.                     long waitForSysproc = 0;
  179.                     const long howLongToWaitForSysproc = 100;
  180.                     isInitialized = true;
  181.                     promptNeedsRedraw = true;
  182.                     string lastCommand = null;
  183.                     while(true) {
  184.                         if(!string.IsNullOrEmpty(currentCommand)) {
  185.                             ignoreErrors = false;
  186.                             ignoreOutput = false;
  187.                             promptNeedsRedraw = true;
  188.                             if(currentCommand == "exit") {
  189.                                 lastCommand = currentCommand;
  190.                                 break;
  191.                             }
  192.                             system_process.StandardInput.WriteLine(currentCommand);
  193.                             system_process.StandardInput.Flush();
  194.                             probablyFinishedCommand = false;
  195.                             lastCommand = currentCommand;
  196.                             currentCommand = "";
  197.                             waitForSysproc = 0;
  198.                         }
  199.                         System.Threading.Thread.Sleep(10);
  200.                         if(waitForSysproc < howLongToWaitForSysproc) {
  201.                             waitForSysproc += 10;
  202.                             if(waitForSysproc >= howLongToWaitForSysproc) {
  203.                                 if(lastCommand == "cd") {
  204.                                     activeDir = PWD();
  205.                                 }
  206.                                 if(!probablyFinishedCommand && cmd != null) {
  207.                                     cmd.NeedToRefreshUserPrompt = true;
  208.                                 }
  209.                                 probablyFinishedCommand = true;
  210.                             }
  211.                         }
  212.                     }
  213.                     system_process.StandardInput.WriteLine("exit");
  214.                     system_process.StandardInput.Flush();
  215.                     System.Diagnostics.Process proc = system_process;
  216.                     System.Threading.Thread t = thread;
  217.                     if(cmd != null) { cmd.NeedToRefreshUserPrompt = true; }
  218.                     thread = null;
  219.                     system_process = null;
  220.                     isInitialized = false;
  221.                     proc.WaitForExit();
  222.                     proc.Close();
  223.                     t.Join(); // should be the last statement
  224.                 });
  225.                 thread.Start();
  226.             } else {
  227.                 if(!string.IsNullOrEmpty(s)) {
  228.                     currentCommand = s;
  229.                     currentCommand_callback = cb;
  230.                 }
  231.             }
  232.         }
  233.  
  234.         private string COMMAND_LINE_GETTER(string call) {
  235.             System.Diagnostics.Process proc = new System.Diagnostics.Process {
  236.                 StartInfo = new System.Diagnostics.ProcessStartInfo {
  237.                     FileName = call,
  238.                     UseShellExecute = false,
  239.                     RedirectStandardOutput = true,
  240.                     RedirectStandardError = true,
  241.                     CreateNoWindow = true,
  242.                 }
  243.             };
  244.             proc.Start();
  245.             string r = proc.StandardOutput.ReadLine();
  246.             return r;
  247.         }
  248.         public string PWD() {
  249.             string pwd = COMMAND_LINE_GETTER("pwd");
  250.             return pwd;
  251.         }
  252.  
  253.         public bool IsProbablyIdle() {
  254.             return (thread == null ||
  255.             (string.IsNullOrEmpty(currentCommand) && probablyFinishedCommand));
  256.         }
  257.  
  258.         public bool IsInitialized() { return isInitialized; }
  259.  
  260.         public string MachineName { get { return system_process.MachineName; } }
  261.  
  262.         public void Update(Instruction inst, CmdLine cmd) {
  263.             bool somethingPrinted = false;
  264.             if(log != null) {
  265.                 while(log.Count > 0) {
  266.                     cmd.HandleLog(log[0], "", LogType.Log);
  267.                     log.RemoveAt(0);
  268.                     somethingPrinted = true;
  269.                 }
  270.             }
  271.             if(err != null) {
  272.                 while(err.Count > 0) {
  273.                     cmd.HandleLog(err[0], "", LogType.Error);
  274.                     err.RemoveAt(0);
  275.                     somethingPrinted = true;
  276.                 }
  277.             }
  278.             string s = null;
  279.             if(inst != null) {
  280.                 s = inst.text;
  281.                 if(s != null) { DoCommand(s, inst.user); }
  282.             }
  283.             if(string.IsNullOrEmpty(s) &&
  284.             string.IsNullOrEmpty(currentCommand) &&
  285.             (somethingPrinted || promptNeedsRedraw)) {
  286.                 cmd.NeedToRefreshUserPrompt = true;
  287.             }
  288.             if(cmd.NeedToRefreshUserPrompt) {
  289.                 promptNeedsRedraw = false;
  290.             }
  291.         }
  292.     }
  293.     private BASH bash;
  294. #endif
  295.     #endregion // os_commandline_terminal
  296.  
  297.     /// <param name="command">name of the command to add (case insensitive)</param>
  298.     /// <param name="handler">code to execute with this command, think standard main</param>
  299.     /// <param name="help">reference information, think concise man-pages. Make help <c>"\b"</c> for hidden commands</param>
  300.     public void addCommand(string command, CommandHandler handler, string help) {
  301.         commands.Add(command.ToLower(), new Command(command, handler, help));
  302.     }
  303.     /// <param name="command">name of the command to add (case insensitive)</param>
  304.     /// <param name="handler">code to execute with this command, think standard main</param>
  305.     /// <param name="help">reference information, think concise man-pages. Make help <c>"\b"</c> for hidden commands</param>
  306.     public static void AddCommand(string command, CommandHandler handler, string help) {
  307.         Instance.addCommand(command, handler, help);
  308.     }
  309.     /// <param name="commands">dictionary of commands to begin using, replacing old commands</param>
  310.     public void SetCommands(Dictionary<string, Command> commands) { this.commands = commands; }
  311.     /// <summary>replace current commands with no commands</summary>
  312.     public void ClearCommands() { commands.Clear(); }
  313.     /// <summary>command-line handler. think "standard main" from Java or C/C++.
  314.     /// args[0] is the command, args[1] and beyond are the arguments.</summary>
  315.     public delegate void CommandHandler(string[] args, object whosAsking);
  316.     public class Command {
  317.         public string command { get; private set; }
  318.         public CommandHandler handler { get; private set; }
  319.         public string help { get; private set; }
  320.         public Command(string command, CommandHandler handler, string helpReferenceText) {
  321.             this.command = command; this.handler = handler; this.help = helpReferenceText;
  322.         }
  323.     }
  324.     /// <returns>a list of usable commands</returns>
  325.     public string CommandHelpString() {
  326.         StringBuilder sb = new StringBuilder();
  327.         foreach(Command cmd in commands.Values) {
  328.             if(cmd.help != "\b") // commands with a single backspace as help text are hidden commands
  329.                 sb.Append(((sb.Length > 0) ? "\n" : "") + cmd.command + ": " + cmd.help);
  330.         }
  331.         return sb.ToString();
  332.     }
  333.     /// <summary>Enqueues a command to run, which will be run during the next Update</summary>
  334.     public static void DoCommand(string commandWithArguments, object fromWho = null) {
  335.         bool isNewInstance = _instance == null;
  336.         Instance.EnqueueRun(new Instruction() { text = commandWithArguments, user = fromWho });
  337.         if(isNewInstance) { Instance.Interactivity = InteractivityEnum.Disabled; }
  338.     }
  339.     /// <summary>Enqueues a command to run, which will be run during the next Update</summary>
  340.     /// <param name="instruction">Command string, with arguments.</param>
  341.     public void EnqueueRun(Instruction instruction) {
  342.         instructionList.Add(instruction);
  343.         if(instruction.IsUser(UserRawInput)) {
  344.             indexWherePromptWasPrintedRecently = -1; // make sure this command stays visible
  345.         }
  346.     }
  347.     private void Run(Instruction instruction) {
  348.         if(waitingToReadLine != null) {
  349.             waitingToReadLine(instruction.text);
  350.             waitingToReadLine = null;
  351.         } else if(onInput != null) {
  352.             onInput(instruction.text);
  353.         } else {
  354.             if(string.IsNullOrEmpty(instruction.text)) { return; }
  355.             string s = instruction.text.Trim(Util.WHITESPACE); // cut leading & trailing whitespace
  356.             string[] args = Util.ParseArguments(s).ToArray();
  357.             if(args.Length < 1) { return; }
  358.             if(OnCommand != null) { OnCommand(args, instruction.user); }
  359.             Run(args[0].ToLower(), args, instruction.user);
  360.         }
  361.     }
  362.     /// <param name="command">Command.</param>
  363.     /// <param name="args">Arguments. [0] is the name of the command, with [1] and beyond being the arguments</param>
  364.     private void Run(string command, string[] args, object user) {
  365.         Command cmd = null;
  366.         // try to find the given command. or the default command. if we can't find either...
  367.         if(!commands.TryGetValue(command, out cmd) && !commands.TryGetValue("", out cmd)) {
  368.             // error!
  369.             string error = "Unknown command '" + NoparseFilterAroundTag(command) + "'";
  370.             if(args.Length > 1) { error += " with arguments "; }
  371.             for(int i = 1; i < args.Length; ++i) {
  372.                 if(i > 1) { error += ", "; }
  373.                 error += "'" + NoparseFilterAroundTag(args[i]) + "'";
  374.             }
  375.             log(error);
  376.         }
  377.         // if we have a command
  378.         if(cmd != null) {
  379.             // execute it if it has valid code
  380.             if(cmd.handler != null) {
  381.                 cmd.handler(args, user);
  382.             } else {
  383.                 log("Null command '" + command + "'");
  384.             }
  385.         }
  386.     }
  387.     #endregion // commands
  388.     #region user interface
  389.     public string PromptArtifact = "$ ";
  390.     [Tooltip("the main viewable UI component")]
  391.     private Canvas _mainView;
  392.     public enum InteractivityEnum { Disabled, ScreenOverlayOnly, WorldSpaceOnly, ActiveScreenAndInactiveWorld };
  393.     [SerializeField]
  394.     private InteractivityEnum interactivity = InteractivityEnum.ActiveScreenAndInactiveWorld;
  395.     public InteractivityEnum Interactivity {
  396.         get { return interactivity; }
  397.         set { interactivity = value; SetInteractive(IsInteractive()); }
  398.     }
  399.     [Tooltip("Which key shows the terminal")]
  400.     public KeyCode KeyToActivate = KeyCode.BackQuote;
  401.     [Tooltip("Which key hides the terminal")]
  402.     public KeyCode KeyToDeactivate = KeyCode.Escape;
  403.     [Tooltip("used to size the console Rect Transform on creation as a UI overlay")]
  404.     public RectTransformSettings ScreenOverlaySettings;
  405.     [Tooltip("fill this out to set the console in the world someplace")]
  406.     public PutItInWorldSpace WorldSpaceSettings = new PutItInWorldSpace(0.005f, Vector2.zero);
  407.     [Tooltip("used to color the console on creation")]
  408.     public InitialColorSettings ColorSet = new InitialColorSettings();
  409.     /// <summary>the real workhorse of this commandline system</summary>
  410.     private TMPro.TMP_InputField _tmpInputField;
  411.     /// <summary>used to prevent multiple simultaneous toggles of visibility</summary>
  412.     private bool _togglingVisiblityWithMultitouch = false;
  413.     [Tooltip("If true, will show up and take user input immediately")]
  414.     public bool ActiveOnStart = true;
  415. #if CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  416.     public bool AllowSystemAccess = true;
  417. #endif
  418.     #region Debug.Log intercept
  419.     [SerializeField, Tooltip("If true, all Debug.Log messages will be intercepted and duplicated here.")]
  420.     private bool interceptDebugLog = false;
  421.     public bool InterceptDebugLog { get { return interceptDebugLog; } set { interceptDebugLog = value; SetDebugLogIntercept(interceptDebugLog); } }
  422.     /// <summary>if this object was intercepting Debug.Logs, this will ensure that it un-intercepts as needed</summary>
  423.     private bool dbgIntercepted = false;
  424.  
  425.     public void EnableDebugLogIntercept() { SetDebugLogIntercept(InterceptDebugLog); }
  426.     public void DisableDebugLogIntercept() { SetDebugLogIntercept(false); }
  427.     public void SetDebugLogIntercept(bool intercept) {
  428. #if UNITY_EDITOR
  429.         if(!Application.isPlaying) return;
  430. #endif
  431.         if(intercept && !dbgIntercepted) {
  432.             Application.logMessageReceived += HandleLog;
  433.             dbgIntercepted = true;
  434.         } else if(!intercept && dbgIntercepted) {
  435.             Application.logMessageReceived -= HandleLog;
  436.             dbgIntercepted = false;
  437.         }
  438.     }
  439.     private void HandleLog(string logString, string stackTrace = "", LogType type = LogType.Log) {
  440.         const string cEnd = "</color>\n";
  441.         switch(type) {
  442.         case LogType.Error:
  443.             AddText("<#" + ColorSet.ErrorTextHex + ">" + logString + cEnd);
  444.             break;
  445.         case LogType.Exception:
  446.             string c = "<#" + ColorSet.ExceptionTextHex + ">";
  447.             AddText(c + logString + cEnd);
  448.             AddText(c + stackTrace + cEnd);
  449.             break;
  450.         case LogType.Warning:
  451.             AddText("<#" + ColorSet.SpecialTextHex + ">" + logString + cEnd);
  452.             break;
  453.         default:
  454.             log(logString);
  455.             break;
  456.         }
  457.     }
  458.     #endregion // Debug.Log intercept
  459.     public bool NeedToRefreshUserPrompt { get; set; }
  460.     /// used to smartly (perhaps overly-smartly) over-write the prompt when printing things out-of-sync
  461.     private int indexWherePromptWasPrintedRecently = -1;
  462.     private const string mainTextObjectName = "MainText";
  463.     [Tooltip("The TextMeshPro font used. If null, built-in-font should be used.")]
  464.     public TMP_FontAsset textMeshProFont;
  465.     public TMP_FontAsset TextMeshProFont {
  466.         get { return textMeshProFont; }
  467.         set {
  468.             textMeshProFont = value;
  469.             if(textMeshProFont != null && _mainView != null) {
  470.                 TMP_Text[] texts = _mainView.GetComponentsInChildren<TMP_Text>();
  471.                 for(int i = 0; i < texts.Length; ++i) {
  472.                     if(texts[i].gameObject.name == mainTextObjectName) {
  473.                         texts[i].font = textMeshProFont; break;
  474.                     }
  475.                 }
  476.             }
  477.         }
  478.     }
  479.     /// <summary>which command line is currently active, and disabling user controls</summary>
  480.     private static CmdLine currentlyActiveCmdLine, disabledUserControls;
  481.     /// <summary>used to check which command line is the best one for the user controlling the main camera</summary>
  482.     private float viewscore;
  483.  
  484.     [System.Serializable]
  485.     public class InitialColorSettings {
  486.         public Color Background = new Color(0, 0, 0, 0.5f);
  487.         public Color Text = new Color(1, 1, 1);
  488.         public Color ErrorText = new Color(1, .5f, .5f);
  489.         public Color SpecialText = new Color(1, .75f, 0);
  490.         public Color ExceptionText = new Color(1, .5f, 1);
  491.         public Color Scrollbar = new Color(1, 1, 1, 0.5f);
  492.         public Color UserInput = new Color(.75f, .875f, .75f);
  493.         public Color UserSelection = new Color(.5f, .5f, 1, .75f);
  494.         public string UserInputHex { get { return CmdLine.Util.ColorToHexCode(UserInput); } }
  495.         public string ErrorTextHex { get { return CmdLine.Util.ColorToHexCode(ErrorText); } }
  496.         public string SpecialTextHex { get { return CmdLine.Util.ColorToHexCode(SpecialText); } }
  497.         public string ExceptionTextHex { get { return CmdLine.Util.ColorToHexCode(ExceptionText); } }
  498.     }
  499.     [System.Serializable]
  500.     public class RectTransformSettings {
  501.         public Vector2 AnchorMin = Vector2.zero;
  502.         public Vector2 AnchorMax = Vector2.one;
  503.         public Vector2 OffsetMin = Vector2.zero;
  504.         public Vector2 OffsetMax = Vector2.zero;
  505.     }
  506.     [System.Serializable]
  507.     public class PutItInWorldSpace {
  508.         [Tooltip("If zero, will automatically set to current Screen's pixel size")]
  509.         public Vector2 screenSize = new Vector2(0, 0);
  510.         [Tooltip("how many meters each pixel should be")]
  511.         public float textScale = 0.005f;
  512.         public PutItInWorldSpace(float scale, Vector2 size) {
  513.             this.textScale = scale;
  514.             this.screenSize = size;
  515.         }
  516.         public void ApplySettingsTo(Canvas c) {
  517.             if(screenSize == Vector2.zero) { screenSize = new Vector2(Screen.width, Screen.height); }
  518.             RectTransform r = c.GetComponent<RectTransform>();
  519.             r.sizeDelta = screenSize;
  520.             c.transform.localPosition = Vector3.zero;
  521.             c.transform.localRotation = Quaternion.identity;
  522.             r.anchoredPosition = Vector2.zero;
  523.             r.localScale = Vector3.one * textScale;
  524.         }
  525.     }
  526.     private void PrintPrompt() {
  527.         int indexBeforePrompt = GetRawText().Length;
  528.         if(indexWherePromptWasPrintedRecently != -1) {
  529.             indexBeforePrompt = indexWherePromptWasPrintedRecently;
  530.         }
  531.         string promptText = PromptArtifact;
  532. #if CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  533.         if(bash.IsInitialized()) {
  534.             promptText = bash.MachineName + PromptArtifact;
  535.         }
  536. #endif
  537.         AddText(promptText);
  538.         indexWherePromptWasPrintedRecently = indexBeforePrompt;
  539.     }
  540.     public bool IsInOverlayMode() {
  541.         return _mainView.renderMode == RenderMode.ScreenSpaceOverlay;
  542.     }
  543.     public void PositionInWorld(Vector3 center, Vector2 size = default(Vector2), float scale = 0.005f) {
  544.         if(size == Vector2.zero) size = new Vector2(Screen.width, Screen.height);
  545.         PutItInWorldSpace ws = new PutItInWorldSpace(scale, size);
  546.         transform.position = center;
  547.         if(_mainView == null) {
  548.             WorldSpaceSettings = ws;
  549.         } else {
  550.             ws.ApplySettingsTo(_mainView);
  551.         }
  552.         RecalculateFontSize();
  553.     }
  554.     private void SetOverlayModeInsteadOfWorld(bool useOverlay) {
  555.         if(useOverlay && _mainView.renderMode != RenderMode.ScreenSpaceOverlay) {
  556.             _mainView.renderMode = RenderMode.ScreenSpaceOverlay;
  557.         } else if(!useOverlay) {
  558.             _mainView.renderMode = RenderMode.WorldSpace;
  559.             WorldSpaceSettings.ApplySettingsTo(_mainView);
  560.             RecalculateFontSize();
  561.         }
  562.     }
  563.     private Canvas CreateUI() {
  564.         _mainView = transform.GetComponentInParent<Canvas>();
  565.         if(!_mainView) {
  566.             _mainView = (new GameObject("canvas")).AddComponent<Canvas>(); // so that the UI can be drawn at all
  567.             _mainView.renderMode = RenderMode.ScreenSpaceOverlay;
  568.             if(!_mainView.GetComponent<CanvasScaler>()) {
  569.                 _mainView.gameObject.AddComponent<CanvasScaler>(); // so that text is pretty when zoomed
  570.             }
  571.             if(!_mainView.GetComponent<GraphicRaycaster>()) {
  572.                 _mainView.gameObject.AddComponent<GraphicRaycaster>(); // so that mouse can select input area
  573.             }
  574.             _mainView.transform.SetParent(transform);
  575.         }
  576.         GameObject tmpGo = new GameObject("TextMeshPro - InputField");
  577.         tmpGo.transform.SetParent(_mainView.transform);
  578.         Image img = tmpGo.AddComponent<Image>();
  579.         img.color = ColorSet.Background;
  580.         if(ScreenOverlaySettings == null) {
  581.             MaximizeRectTransform(tmpGo.transform);
  582.         } else {
  583.             RectTransform r = tmpGo.GetComponent<RectTransform>();
  584.             r.anchorMin = ScreenOverlaySettings.AnchorMin;
  585.             r.anchorMax = ScreenOverlaySettings.AnchorMax;
  586.             r.offsetMin = ScreenOverlaySettings.OffsetMin;
  587.             r.offsetMax = ScreenOverlaySettings.OffsetMax;
  588.         }
  589.         _tmpInputField = tmpGo.AddComponent<TMP_InputField>();
  590.         _tmpInputField.lineType = TMP_InputField.LineType.MultiLineNewline;
  591.         _tmpInputField.textViewport = _tmpInputField.GetComponent<RectTransform>();
  592.         TextMeshProUGUI tmpText;
  593. #if UNITY_EDITOR
  594.         try {
  595. #endif
  596.             tmpText = (new GameObject(mainTextObjectName)).AddComponent<TextMeshProUGUI>();
  597. #if UNITY_EDITOR
  598.         } catch(System.Exception) {
  599.             throw new System.Exception("Could not create a TextMeshProUGUI object. Did you get default fonts into TextMeshPro? Window -> TextMeshPro -> Import TMP Essential Resources");
  600.         }
  601. #endif
  602.         if(textMeshProFont != null) {
  603.             tmpText.font = textMeshProFont;
  604.         }
  605.         tmpText.fontSize = 20;
  606.         tmpText.transform.SetParent(tmpGo.transform);
  607.         _tmpInputField.textComponent = tmpText;
  608.         _tmpInputField.fontAsset = tmpText.font;
  609.         _tmpInputField.pointSize = tmpText.fontSize;
  610.         MaximizeRectTransform(tmpText.transform);
  611.  
  612.         tmpGo.AddComponent<RectMask2D>();
  613.         _tmpInputField.onFocusSelectAll = false;
  614.         tmpText.color = ColorSet.Text;
  615.         _tmpInputField.selectionColor = ColorSet.UserSelection;
  616.         _tmpInputField.customCaretColor = true;
  617.         _tmpInputField.caretColor = ColorSet.UserInput;
  618.         _tmpInputField.caretWidth = 5;
  619.         _tmpInputField.ActivateInputField();
  620.         _tmpInputField.onValueChanged.AddListener(listener_OnValueChanged);
  621.         _tmpInputField.characterValidation = TMP_InputField.CharacterValidation.CustomValidator;
  622.         _tmpInputField.inputValidator = GetInputValidator();
  623.  
  624.         if(_tmpInputField.verticalScrollbar == null) {
  625.             GameObject scrollbar = new GameObject("scrollbar vertical");
  626.             scrollbar.transform.SetParent(_tmpInputField.transform);
  627.             scrollbar.AddComponent<RectTransform>();
  628.             _tmpInputField.verticalScrollbar = scrollbar.AddComponent<Scrollbar>();
  629.             _tmpInputField.verticalScrollbar.direction = Scrollbar.Direction.TopToBottom;
  630.             RectTransform r = scrollbar.GetComponent<RectTransform>();
  631.             r.anchorMin = new Vector2(1, 0);
  632.             r.anchorMax = Vector2.one;
  633.             r.offsetMax = Vector3.zero;
  634.             r.offsetMin = new Vector2(-16, 0);
  635.         }
  636.         if(_tmpInputField.verticalScrollbar.handleRect == null) {
  637.             GameObject slideArea = new GameObject("sliding area");
  638.             slideArea.transform.SetParent(_tmpInputField.verticalScrollbar.transform);
  639.             RectTransform r = slideArea.AddComponent<RectTransform>();
  640.             MaximizeRectTransform(slideArea.transform);
  641.             r.offsetMin = new Vector2(10, 10);
  642.             r.offsetMax = new Vector2(-10, -10);
  643.             GameObject handle = new GameObject("handle");
  644.             Image bimg = handle.AddComponent<Image>();
  645.             bimg.color = ColorSet.Scrollbar;
  646.             handle.transform.SetParent(slideArea.transform);
  647.             r = handle.GetComponent<RectTransform>();
  648.             r.anchorMin = r.anchorMax = Vector2.zero;
  649.             r.offsetMax = new Vector2(5, 5);
  650.             r.offsetMin = new Vector2(-5, -5);
  651.             r.pivot = Vector2.one;
  652.             _tmpInputField.verticalScrollbar.handleRect = r;
  653.             _tmpInputField.verticalScrollbar.targetGraphic = img;
  654.         }
  655.         // an event system is required... if there isn't one, make one
  656.         StandaloneInputModule input = FindObjectOfType(typeof(StandaloneInputModule)) as StandaloneInputModule;
  657.         if(input == null) {
  658.             input = (new GameObject("<EventSystem>")).AddComponent<StandaloneInputModule>();
  659.         }
  660.         // put all UI in the UI layer
  661.         Util.SetLayerRecursive(_mainView.gameObject, LayerMask.NameToLayer("UI"));
  662.         // turn it off and then back on again... that fixes some things.
  663.         tmpGo.SetActive(false); tmpGo.SetActive(true);
  664.         // put it in the world (if needed)
  665.         if(Interactivity == InteractivityEnum.WorldSpaceOnly
  666.         || Interactivity == InteractivityEnum.ActiveScreenAndInactiveWorld) {
  667.             WorldSpaceSettings.ApplySettingsTo(_mainView);
  668.             RecalculateFontSize();
  669.         }
  670.         // TODO find a way to get words to not word-wrap around word boundaries, but instead to split on characters.
  671.         return _mainView;
  672.     }
  673.     private float CalculateIdealFontSize(TMP_Text tmpText, float idealCharsPerLine) {
  674.         float normalCharacterWidth = tmpText.font.characterDictionary[(int)'e'].xAdvance;
  675.         float idealFontSize = (WorldSpaceSettings.screenSize.x * tmpText.font.fontInfo.PointSize) / (idealCharsPerLine * normalCharacterWidth);
  676.         return idealFontSize;
  677.     }
  678.     private void RecalculateFontSize() {
  679.         TMP_Text tmpText = _tmpInputField.textComponent;
  680.         tmpText.fontSize = CalculateIdealFontSize(tmpText, idealLettersPerLine + .125f);
  681.     }
  682.     private static RectTransform MaximizeRectTransform(Transform t) {
  683.         return MaximizeRectTransform(t.GetComponent<RectTransform>());
  684.     }
  685.     private static RectTransform MaximizeRectTransform(RectTransform r) {
  686.         r.anchorMax = Vector2.one;
  687.         r.anchorMin = r.offsetMin = r.offsetMax = Vector2.zero;
  688.         return r;
  689.     }
  690.     public bool IsVisible() {
  691.         return _mainView != null && _mainView.gameObject.activeInHierarchy;
  692.     }
  693.     /// <summary>shows (true) or hides (false).</summary>
  694.     public void SetVisibility(bool visible) {
  695.         if(_mainView == null) {
  696.             ActiveOnStart = visible;
  697.         } else {
  698.             _mainView.gameObject.SetActive(visible);
  699.         }
  700.     }
  701.     /// <param name="enabled">If <c>true</c>, reads from keybaord. if <c>false</c>, stops reading from keyboard</param>
  702.     public void SetInputActive(bool enabled) {
  703.         if(enabled) { _tmpInputField.ActivateInputField(); } else { _tmpInputField.DeactivateInputField(); }
  704.     }
  705.     /// <param name="enableInteractive"><c>true</c> to turn this on (and turn the previous CmdLine off)</param>
  706.     public void SetInteractive(bool enableInteractive) {
  707.         if(_mainView == null && Interactivity != InteractivityEnum.Disabled) {
  708.             CreateUI();
  709.             if(nonUserInput.Length == 0) { log(Application.productName + ", v" + Application.version); } else { setText(nonUserInput); }
  710.         }
  711.         if(_tmpInputField == null) { return; }
  712.         bool activityWhenStarted = _tmpInputField.interactable;
  713.         if(enableInteractive && currentlyActiveCmdLine != null) {
  714.             currentlyActiveCmdLine.SetInteractive(false);
  715.         }
  716.         _tmpInputField.interactable = enableInteractive; // makes focus possible
  717.         switch(Interactivity) {
  718.         case InteractivityEnum.Disabled:
  719.             SetVisibility(false);
  720.             break;
  721.         case InteractivityEnum.ScreenOverlayOnly:
  722.             if(!IsInOverlayMode()) {
  723.                 SetOverlayModeInsteadOfWorld(true);
  724.             }
  725.             SetVisibility(enableInteractive);
  726.             break;
  727.         case InteractivityEnum.WorldSpaceOnly:
  728.             if(!IsVisible()) {
  729.                 SetVisibility(true);
  730.             }
  731.             if(enableInteractive)
  732.                 SetOverlayModeInsteadOfWorld(false);
  733.             break;
  734.         case InteractivityEnum.ActiveScreenAndInactiveWorld:
  735.             //Debug.Log("switching "+ enableInteractive);
  736.             if(!IsVisible()) {
  737.                 SetVisibility(true);
  738.             }
  739.             SetOverlayModeInsteadOfWorld(enableInteractive);
  740.             break;
  741.         }
  742.         _tmpInputField.verticalScrollbar.value = 1; // scroll to the bottom
  743.         MoveCaretToEnd(); // move caret focus to end
  744.         SetInputActive(_tmpInputField.interactable); // request/deny focus
  745.         if(enableInteractive) {
  746.             currentlyActiveCmdLine = this;
  747.         } else if(currentlyActiveCmdLine == this) {
  748.             // if this command line has disabled the user
  749.             if(disabledUserControls == currentlyActiveCmdLine) {
  750.                 // tell it to re-enable controls
  751.                 if(!callbacks.ignoreCallbacks && callbacks.whenThisDeactivates != null) callbacks.whenThisDeactivates.Invoke();
  752.                 disabledUserControls = null;
  753.             }
  754.             currentlyActiveCmdLine = null;
  755.         }
  756.     }
  757.     public bool IsInteractive() { return _tmpInputField != null && _tmpInputField.interactable; }
  758.     /// <summary>Moves the caret to the end, clearing all selections in the process</summary>
  759.     public void MoveCaretToEnd() {
  760.         int lastPoint = GetRawText().Length;
  761.         SetCaretPosition(lastPoint);
  762.     }
  763.     #endregion // user interface
  764.     #region input validation
  765.     /// <summary>console data that should not be modifiable as user input. Can be added to before the command-line even has UI components like _tmpInputField.</summary>
  766.     private string nonUserInput = "";
  767.     private CmdLineValidator inputvalidator;
  768.     /// <summary>keeps track of user selection so that the text field can be fixed if selected text is removed</summary>
  769.     private int selectBegin, selectEnd;
  770.     /// <summary>what replaces an attempt to un-escape the TextMeshPro noparse boundary in the command line</summary>
  771.     public const string NOPARSE_REPLACEMENT = ">NOPARSE<";
  772.     /// <summary>flag to move text view to the bottom when content is added</summary>
  773.     private bool showBottomWhenTextIsAdded = false;
  774.     /// <summary>if text is being modified to refresh it after the user did something naughty</summary>
  775.     private bool addingOnChanged = false;
  776.     [Tooltip("Maximum number of lines to retain.")]
  777.     public int maxLines = 99;
  778.     [Tooltip("lines with more characters than this will count as more than one line.")]
  779.     public int idealLettersPerLine = 80;
  780.  
  781.     private CmdLineValidator GetInputValidator() {
  782.         if(inputvalidator == null) {
  783.             inputvalidator = ScriptableObject.CreateInstance<CmdLineValidator>();
  784.             inputvalidator.Init(this);
  785.         }
  786.         return inputvalidator;
  787.     }
  788.     private void listener_OnTextSelectionChange(string str, int start, int end) {
  789.         selectBegin = Math.Min(start, end);
  790.         selectEnd = Math.Max(start, end);
  791.     }
  792.     /// <summary>the class that tries to keep the user from wrecking the command line terminal</summary>
  793.     private class CmdLineValidator : TMP_InputValidator {
  794.         public CmdLine cmd;
  795.         private TMP_InputField inputField;
  796.         public bool isUserEnteringInput = false;
  797.         public void Init(CmdLine cmd) {
  798.             this.cmd = cmd;
  799.             this.inputField = cmd._tmpInputField;
  800.         }
  801.         public void AddUserInput(string userInput) {
  802.             string s = inputField.text;
  803.             int cursor = inputField.caretPosition; // should this be caretPosition?
  804.             for(int i = 0; i < userInput.Length; ++i) {
  805.                 char c = userInput[i];
  806.                 Validate(ref s, ref cursor, c);
  807.             }
  808.             inputField.text = s;
  809.             inputField.caretPosition = cursor;
  810.         }
  811.         int AddUserInput(ref string text, char letter) {
  812.             int added = 0;
  813.             if(!isUserEnteringInput) {
  814.                 isUserEnteringInput = true;
  815.                 string headr = BEGIN_USER_INPUT();
  816.                 text += headr;
  817.                 added += headr.Length;
  818.                 cmd.nonUserInput = text;
  819.             }
  820.             text += letter; added += 1;
  821.             return added;
  822.         }
  823.         public int EndUserInput(bool forced) {
  824.             if(forced)
  825.                 isUserEnteringInput = true;
  826.             string s = inputField.text;
  827.             int returned = EndUserInput(ref s);
  828.             inputField.text = s;
  829.             return returned;
  830.         }
  831.         //public enum ProperInputTagState { missing, has, malformed };
  832.         public bool HasProperInputTags(string text) {
  833.             List<string> tags = new List<string>();
  834.             Util.CalculateTextMeshProTags(text, false, tags);
  835.             if(tags.Count == 0 || !tags.Contains("noparse"))
  836.                 return false;
  837.             string colorTag = "#" + cmd.ColorSet.UserInputHex;
  838.             return tags.Contains(colorTag);
  839.         }
  840.         public bool CheckIfUserInputTagsArePresent(string text) {
  841.             string beg = BEGIN_USER_INPUT();
  842.             int len = beg.Length, pos = cmd.GetCaretPosition();
  843.             if(pos >= len) {
  844.                 if(text.Substring(pos - len).Contains(beg)) {
  845.                     isUserEnteringInput = true;
  846.                 }else {
  847.                     // check if the new text has the input tags opened
  848.                     isUserEnteringInput = HasProperInputTags(text);
  849.                 }
  850.             } else {
  851.                 isUserEnteringInput = false;
  852.             }
  853.             return isUserEnteringInput;
  854.         }
  855.         public string BEGIN_USER_INPUT() {
  856.             return "<#" + cmd.ColorSet.UserInputHex + "><noparse>";
  857.         }
  858.         public string END_USER_INPUT() { return "</noparse></color>"; }
  859.         private int EndUserInput(ref string text) {
  860.             int added = 0;
  861.             if(isUserEnteringInput) {
  862.                 isUserEnteringInput = false;
  863.                 string expectedheadr = BEGIN_USER_INPUT();
  864.                 if(text.EndsWith(expectedheadr)) {
  865.                     text = text.Substring(0, text.Length - expectedheadr.Length);
  866.                     added -= expectedheadr.Length;
  867.                 } else {
  868.                     string footr = END_USER_INPUT();
  869.                     text += footr;
  870.                     added += footr.Length;
  871.                 }
  872.                 cmd.nonUserInput = text;
  873.             }
  874.             return added;
  875.         }
  876.         public override char Validate(ref string text, ref int pos, char ch) {
  877.             int posAtStart = pos;
  878.             if(!cmd.IsInteractive()) return '\0';
  879.             char letter = '\0';
  880.             if(pos < text.Length) {
  881.                 letter = text[pos];
  882.             }
  883.             if(pos < cmd.nonUserInput.Length) {
  884.                 pos = cmd.GetRawText().Length;
  885.             }
  886.             pos += AddUserInput(ref text, ch);
  887.             // if the user is attempting to break out of noparse...
  888.             if(ch == '>') {
  889.                 // check if a tag is being ended
  890.                 int startOfTag = text.LastIndexOf('<');
  891.                 int endOfTag = text.LastIndexOf('>', text.Length - 2);
  892.                 if(startOfTag >= 0 && startOfTag > endOfTag) {
  893.                     string possibleTag = text.Substring(startOfTag).ToLower();
  894.                     // unescape, incase the user is being trixie with unescape sequences...
  895.                     possibleTag = Util.Unescape(possibleTag);
  896.                     // and if they are, just don't let them.
  897.                     if(possibleTag.Contains("noparse")) {
  898.                         text = text.Substring(0, startOfTag) + NOPARSE_REPLACEMENT;
  899.                     }
  900.                 }
  901.             }
  902.             // if the user wants to execute (because they pressed enter)
  903.             else if(ch == '\n') {
  904.                 object whoExecutes = cmd.UserRawInput; // the user-controlled input field
  905.                 string inpt = cmd.GetUserInput();
  906.                 int start = 0, end = -1;
  907.                 do {
  908.                     end = inpt.IndexOf("\n", start);
  909.                     if(end >= start && start < inpt.Length) {
  910.                         int len = end - start;
  911.                         if(len > 0) {
  912.                             cmd.EnqueueRun(new Instruction() { text = inpt.Substring(start, len), user = whoExecutes });
  913.                         }
  914.                         start = end + 1;
  915.                     }
  916.                 } while(end > 0);
  917.                 if(start < inpt.Length) {
  918.                     cmd.EnqueueRun(new Instruction() { text = inpt.Substring(start), user = whoExecutes });
  919.                 }
  920.                 EndUserInput(ref text);
  921.             }
  922.             // if a bunch of letters were was added (either paste, or new user input)
  923.             if(pos != posAtStart && pos > posAtStart+1) {
  924.                 // recalculate invisible string locations.
  925.                 Util.CalculateTextMeshProTags(text, false, null, cmd.invisibleSubstrings);
  926.             }
  927.             return '\0';
  928.         }
  929.     }
  930.     private void listener_OnValueChanged(string str) {
  931.         if(addingOnChanged) return;
  932.         addingOnChanged = true;
  933.         string newAddition = Input.inputString;
  934.         // don't allow output text to be modified.
  935.         if(GetCaretPosition() < nonUserInput.Length) {
  936.             //int offset = selectBegin - selectEnd;
  937.             //string alreadyTyped = GetUserInput(offset);
  938.             setText(nonUserInput);
  939.             //Debug.Log("draining "+alreadyTyped);
  940.             MoveCaretToEnd();
  941.         }
  942.         addingOnChanged = false;
  943.     }
  944.     private void EndUserInputIfNeeded() {
  945.         if(GetInputValidator().isUserEnteringInput) {
  946.             inputvalidator.isUserEnteringInput = false;
  947.             setText(GetAllText() + inputvalidator.END_USER_INPUT());
  948.         }
  949.     }
  950.     /// <summary>if the given text is a tag, returns the tag with noparse around it.</summary>
  951.     private string NoparseFilterAroundTag(string text) {
  952.         if(text.IndexOf('<') < 0) return text;
  953.         return "<noparse>" + text + "</noparse>";
  954.     }
  955.     private int CutoffIndexToEnsureLineCount(String s, int a_maxLines) {
  956.         int lineCount = 0, columnCount = 0, index;
  957.         // TODO ignore invisibleTextEntries
  958.         for(index = s.Length; index > 0; --index) {
  959.             if(s[index - 1] == '\n' || columnCount++ >= idealLettersPerLine) {
  960.                 lineCount++;
  961.                 columnCount = 0;
  962.                 if(lineCount >= a_maxLines) { break; }
  963.             }
  964.         }
  965.         return index;
  966.     }
  967.     public int GetUserInputLength() {
  968.         if(_tmpInputField == null) return 0;
  969.         string s = GetRawText();
  970.         return s.Length - (nonUserInput.Length);
  971.     }
  972.     public string GetUserInput() {
  973.         return GetUserInput(0);
  974.     }
  975.     /// <returns>The user input, which is text that the user has entered (at the bottom)</returns>
  976.     private string GetUserInput(int offset) {
  977.         string s = GetRawText();
  978.         int len = s.Length - (nonUserInput.Length + offset);
  979.         return (len > 0) ? s.Substring(nonUserInput.Length + offset, len) : "";
  980.     }
  981.     public struct Substring : IComparable {
  982.         public int index, count;
  983.         public int Limit { get { return index + count; } }
  984.         public int Middle { get { return index + count / 2; } }
  985.         public bool Contains(int index){ return this.index <= index && index < Limit; }
  986.         public int CompareTo(object obj) {
  987.             Substring other = (Substring)obj;
  988.             if(other.index < index) return 1;
  989.             if(other.index > index) return -1;
  990.             return 0;
  991.         }
  992.         public string Of(string s) { return s.Substring(index, count); }
  993.     }
  994.  
  995.     // TODO linebreaks every 80 visible characters
  996.     // TODO GetVisibleText(), which strips out the invisible text
  997.     // TODO ConvertRealIndexToVisibleIndex(int realIndex)
  998.     // TODO ConvertVisibleIndexToRealIndex(int visibleIndex)
  999.     public List<Substring> invisibleSubstrings = new List<Substring>();
  1000.  
  1001.     public int WhichInvisibleSubstring(int stringIndex) {
  1002.         Substring te = new Substring { index = stringIndex, count = 0 };
  1003.         int index = invisibleSubstrings.BinarySearch(te);
  1004.         if(index < 0) {
  1005.             index = ~index;
  1006.             if(index > 0 && invisibleSubstrings[index-1].Contains(stringIndex)){
  1007.                 index = index - 1;
  1008.             } else {
  1009.                 index = -1;
  1010.             }
  1011.         }
  1012.         //int whichSubstring = -1;
  1013.         //for(int i = 0; i < invisibleSubstrings.Count; ++i){
  1014.         //  if(invisibleSubstrings[i].Contains(stringIndex)) { return i; }
  1015.         //  if(invisibleSubstrings[i].index > stringIndex) { break; }
  1016.         //}
  1017.         //if(index != whichSubstring){
  1018.         //  Debug.Log(index + " != " + whichSubstring);
  1019.         //  Debug.Log(OMU.Util.ToScript(invisibleSubstrings));
  1020.         //}
  1021.         //return whichSubstring;
  1022.         return index;
  1023.     }
  1024.  
  1025.     private void AddInvisibleSubstring(int textIndex, int count) {
  1026.         Substring te = new Substring { index = textIndex, count = count };
  1027.         int whichSubstring = invisibleSubstrings.BinarySearch(te);
  1028.         if(whichSubstring < 0) {
  1029.             whichSubstring = ~whichSubstring;
  1030.         }
  1031.         ShiftInvisibleSubstringsFrom(whichSubstring, count);
  1032.         bool mergedIn = false;
  1033.         if(whichSubstring > 0) {
  1034.             Substring prev = invisibleSubstrings[whichSubstring - 1];
  1035.             if(prev.Limit == te.index) {
  1036.                 prev.count += te.count;
  1037.                 invisibleSubstrings[whichSubstring - 1] = prev;
  1038.                 mergedIn = true;
  1039.                 ShiftInvisibleSubstringsFrom(whichSubstring, te.count);
  1040.             }
  1041.         } else if(whichSubstring < invisibleSubstrings.Count) {
  1042.             Substring alreadyHere = invisibleSubstrings[whichSubstring];
  1043.             if(te.index == alreadyHere.index) {
  1044.                 alreadyHere.count += te.count;
  1045.                 invisibleSubstrings[whichSubstring] = alreadyHere;
  1046.                 mergedIn = true;
  1047.                 ShiftInvisibleSubstringsFrom(whichSubstring + 1, te.count);
  1048.             }
  1049.         }
  1050.         if(!mergedIn) {
  1051.             invisibleSubstrings.Insert(whichSubstring, te);
  1052.             ShiftInvisibleSubstringsFrom(whichSubstring + 1, te.count);
  1053.         }
  1054.     }
  1055.     private void ShiftInvisibleSubstringsFrom(int listIndex, int delta){
  1056.         for(int i = listIndex; i < invisibleSubstrings.Count; i++){
  1057.             Substring te = invisibleSubstrings[i];
  1058.             te.index += delta;
  1059.             invisibleSubstrings[i] = te;
  1060.         }
  1061.     }
  1062.  
  1063.     private void CalculateInvisibleSubstrings(){
  1064.         Util.CalculateTextMeshProTags(GetAllText(), true, null, invisibleSubstrings);
  1065.     }
  1066.  
  1067.     /// <param name="text">What the the output text should be (turns current user input into text output)</param>
  1068.     public void setText(string text) {
  1069.         int cutIndex = CutoffIndexToEnsureLineCount(text, maxLines);
  1070.         List<string> tags = null;
  1071.         if(cutIndex != 0) {
  1072.             invisibleSubstrings.Clear();
  1073.             tags = new List<string>();
  1074.             Util.CalculateTextMeshProTags(text.Substring(0, cutIndex), false, tags, invisibleSubstrings);
  1075.             text = text.Substring(cutIndex);
  1076.             if(tags != null && tags.Count > 0) {
  1077.                 string openingTags = "";
  1078.                 for(int i = 0; i < tags.Count; ++i) {
  1079.                     openingTags += "<" + tags[i] + ">";
  1080.                 }
  1081.                 text = openingTags + text;
  1082.             }
  1083.             indexWherePromptWasPrintedRecently -= cutIndex;
  1084.         } else {
  1085.             Util.CalculateTextMeshProTags(text, false, null, invisibleSubstrings);
  1086.         }
  1087.         nonUserInput = text;
  1088.         SetRawText(nonUserInput);
  1089.         // if text is replaced during input, this refreshes tags around input
  1090.         if(inputvalidator != null) {
  1091.             inputvalidator.CheckIfUserInputTagsArePresent(text);
  1092.         }
  1093.     }
  1094.     #endregion // input validation
  1095.     #region singleton
  1096.     /// <summary>the singleton instance. One will be created if none exist.</summary>
  1097.     private static CmdLine _instance;
  1098.     public static CmdLine Instance {
  1099.         get {
  1100.             if(_instance == null && (_instance = FindObjectOfType(typeof(CmdLine)) as CmdLine) == null) {
  1101.                 GameObject g = new GameObject();
  1102.                 _instance = g.AddComponent<CmdLine>();
  1103.                 g.name = "<" + _instance.GetType().Name + ">";
  1104. #if UNITY_EDITOR && UNKNOWN_CMDLINE_APPEARS
  1105.                 _instance.whereItWasStarted = Environment.StackTrace;
  1106. #endif
  1107.             }
  1108.             return _instance;
  1109.         }
  1110.     }
  1111. #if UNITY_EDITOR && UNKNOWN_CMDLINE_APPEARS
  1112.     public string whereItWasStarted;
  1113. #endif
  1114.     #endregion // singleton
  1115.     #region static utility functions
  1116.     public static class Util {
  1117.         /// <param name="layer">what Unity layer to set the given object, and all child objects, recursive</param>
  1118.         public static void SetLayerRecursive(GameObject go, int layer) {
  1119.             go.layer = layer;
  1120.             for(int i = 0; i < go.transform.childCount; ++i) {
  1121.                 Transform t = go.transform.GetChild(i);
  1122.                 if(t != null) {
  1123.                     SetLayerRecursive(t.gameObject, layer);
  1124.                 }
  1125.             }
  1126.         }
  1127.         private static string[] singleTagsTMP = { "br", "page" };
  1128.         private static string[] tagsTMP = { "align", "alpha", "b", "br", "color", "cspace", "font", "i", "indent", "line-height", "line-indent", "link", "lowercase", "margin", "mark", "mspace", "noparse", "nobr", "page", "pos", "size", "space", "sprite", "s", "smallcaps", "style", "sub", "sup", "u", "uppercase", "voffset", "width" };
  1129.         private static string[] tagsTMPallowed = { "alpha", "b", "br", "color", "font", "i", "link", "lowercase", "mark", "mspace", "noparse", "nobr", "page", "sprite", "s", "style", "u", "uppercase"};
  1130.         /// <returns>A list of the open/close tags in the given strings</returns>
  1131.         /// <param name="str">where to look for tags</param>
  1132.         /// <param name="keepClosedTags">If <c>false</c>, remove correctly closed tags</param>
  1133.         public static void CalculateTextMeshProTags(string str, bool keepClosedTags = true,
  1134.             List<string> tags = null, List<Substring> indexOfEach = null) {
  1135.             if(indexOfEach != null){ indexOfEach.Clear(); }
  1136.             bool noparse = false;
  1137.             int startIndex, end, trueTokenLength;
  1138.             for(int i = 0; i < str.Length; ++i) {
  1139.                 char c = str[i];
  1140.                 startIndex = i;
  1141.                 if(c == '<') {
  1142.                     end = str.IndexOf('>', i);
  1143.                     trueTokenLength = end - startIndex + 1; // +1 includes the last '>'
  1144.                     string token = null;
  1145.                     if(end > 0) {
  1146.                         // just get the starting token, ignore properties after the first space
  1147.                         int space = str.IndexOf(' ', i);
  1148.                         if(space >= 0 && space < end) { end = space; }
  1149.                         token = str.Substring(i + 1, end - (i + 1));
  1150.                     }
  1151.                     // if noparse is one of the tags, ignore all other tags till noparse is closed.
  1152.                     if(noparse) {
  1153.                         if(token != null && token.Trim() == "/noparse") {
  1154.                             noparse = false;
  1155.                         } else {
  1156.                             token = null;
  1157.                         }
  1158.                     }
  1159.                     if(!noparse && token != null && token.Trim() == "noparse") {
  1160.                         noparse = true;
  1161.                     }
  1162.                     if(token != null && token[0] != '#' && token[0] != '/' && Array.IndexOf(tagsTMPallowed, token) < 0) {
  1163.                         Debug.LogWarning("Probably erroneous tag '" + token + "' found at index " + startIndex);
  1164.                         if(Array.IndexOf(tagsTMP, token) >= 0) {
  1165.                             Debug.LogWarning("CmdLine is explicitly not encouraging use of '" + token + "'");
  1166.                         }
  1167.                     }
  1168.                     if(token != null){
  1169.                         if(indexOfEach != null) {
  1170.                             int lastIndex = indexOfEach.Count - 1;
  1171.                             if(lastIndex >= 0 && indexOfEach[lastIndex].Limit == startIndex) {
  1172.                                 Substring te = indexOfEach[lastIndex];
  1173.                                 te.count += trueTokenLength;
  1174.                                 indexOfEach[lastIndex] = te;
  1175.                             } else {
  1176.                                 indexOfEach.Add(new Substring { index = startIndex, count = trueTokenLength });
  1177.                             }
  1178.                         }
  1179.                     }
  1180.                     if(tags != null) {
  1181.                         if(!keepClosedTags && token != null) {
  1182.                             if(token.StartsWith("/") && tags.Count > 0) {
  1183.                                 int whichTag = tags.LastIndexOf(token.Substring(1));
  1184.                                 if(token == "/color") {
  1185.                                     for(int e = tags.Count - 1; e >= 0; --e) {
  1186.                                         if(tags[e].StartsWith("#")) {
  1187.                                             whichTag = e; break;
  1188.                                         }
  1189.                                     }
  1190.                                 }
  1191.                                 if(whichTag >= 0) {
  1192.                                     tags.RemoveAt(whichTag);
  1193.                                     token = null;
  1194.                                 } else {
  1195.                                     Debug.LogWarning("Unexpected closing tag " + token + " found at index " + startIndex);
  1196.                                 }
  1197.                             } else if(token.EndsWith("/") || System.Array.IndexOf(singleTagsTMP, token) != 0) {
  1198.                                 token = null;
  1199.                             }
  1200.                         }
  1201.                         if(token != null) { tags.Add(token); }
  1202.                     }
  1203.                 }
  1204.             }
  1205.             //if(indexOfEach != null) {
  1206.             //  for(int i = indexOfEach.Count - 1; i > 0; --i) {
  1207.             //      Substring here = indexOfEach[i];
  1208.             //      Substring prev = indexOfEach[i - 1];
  1209.             //      if(here.index == prev.Limit) {
  1210.             //          Debug.Log("merging " + str.Substring(here.index, here.count));
  1211.             //          prev.count += here.count;
  1212.             //          indexOfEach[i - 1] = prev;
  1213.             //          indexOfEach.RemoveAt(i);
  1214.             //      }
  1215.             //  }
  1216.             //  string outp = "";
  1217.             //  for(int i = 0; i < indexOfEach.Count; ++i) {
  1218.             //      outp += indexOfEach[i].index + " '" + indexOfEach[i].Of(str) +"' ->"+ indexOfEach[i].Limit+"\n";
  1219.             //  }
  1220.             //  Debug.Log(str+"\n"+outp);
  1221.             //}
  1222.         }
  1223.         public static string ColorToHexCode(Color c) {
  1224.             int r = (int)(255 * c.r), g = (int)(255 * c.g), b = (int)(255 * c.b), a = (int)(255 * c.a);
  1225.             return r.ToString("X2") + g.ToString("X2") + b.ToString("X2") + ((c.a != 1) ? a.ToString("X2") : "");
  1226.         }
  1227.         public static readonly char[] QUOTES = { '\'', '\"' },
  1228.         WHITESPACE = { ' ', '\t', '\n', '\b', '\r' };
  1229.         /// <returns>index of the end of the token that starts at the given index 'i'</returns>
  1230.         public static int FindEndArgumentToken(string str, int i) {
  1231.             bool isWhitespace;
  1232.             do {
  1233.                 isWhitespace = System.Array.IndexOf(WHITESPACE, str[i]) >= 0;
  1234.                 if(isWhitespace) { ++i; }
  1235.             } while(isWhitespace && i < str.Length);
  1236.             int index = System.Array.IndexOf(QUOTES, str[i]);
  1237.             char startQuote = (index >= 0) ? QUOTES[index] : '\0';
  1238.             if(startQuote != '\0') { ++i; }
  1239.             while(i < str.Length) {
  1240.                 if(startQuote != '\0') {
  1241.                     if(str[i] == '\\') {
  1242.                         i++; // skip the next character for an escape sequence. just leave it there.
  1243.                     } else {
  1244.                         index = System.Array.IndexOf(QUOTES, str[i]);
  1245.                         bool endsQuote = index >= 0 && QUOTES[index] == startQuote;
  1246.                         if(endsQuote) { i++; break; }
  1247.                     }
  1248.                 } else {
  1249.                     isWhitespace = System.Array.IndexOf(WHITESPACE, str[i]) >= 0;
  1250.                     if(isWhitespace) { break; }
  1251.                 }
  1252.                 i++;
  1253.             }
  1254.             if(i >= str.Length) { i = str.Length; }
  1255.             return i;
  1256.         }
  1257.         /// <returns>split command-line arguments</returns>
  1258.         public static List<string> ParseArguments(string commandLineInput) {
  1259.             int index = 0;
  1260.             List<string> tokens = new List<string>();
  1261.             while(index < commandLineInput.Length) {
  1262.                 int end = FindEndArgumentToken(commandLineInput, index);
  1263.                 if(index != end) {
  1264.                     string token = commandLineInput.Substring(index, end - index).TrimStart(WHITESPACE);
  1265.                     token = Unescape(token);
  1266.                     int qi = System.Array.IndexOf(QUOTES, token[0]);
  1267.                     if(qi >= 0 && token[token.Length - 1] == QUOTES[qi]) {
  1268.                         token = token.Substring(1, token.Length - 2);
  1269.                     }
  1270.                     tokens.Add(token);
  1271.                 }
  1272.                 index = end;
  1273.             }
  1274.             return tokens;
  1275.         }
  1276.         /* https://msdn.microsoft.com/en-us/library/aa691087(v=vs.71).aspx */
  1277.         private static readonly SortedDictionary<char, char> EscapeMap = new SortedDictionary<char, char> {
  1278.         { '0','\0' }, { 'a','\a' }, { 'b','\b' }, { 'f','\f' }, { 'n','\n' }, { 'r','\r' }, { 't','\t' }, { 'v','\v' } };
  1279.         /// <summary>convenience method to un-escape standard escape sequence strings</summary>
  1280.         /// <param name="escaped">Escaped.</param>
  1281.         public static string Unescape(string escaped) {
  1282.             if(escaped == null) { return escaped; }
  1283.             StringBuilder sb = new StringBuilder();
  1284.             bool inEscape = false;
  1285.             int startIndex = 0;
  1286.             for(int i = 0; i < escaped.Length; i++) {
  1287.                 if(!inEscape) {
  1288.                     inEscape = escaped[i] == '\\';
  1289.                 } else {
  1290.                     char c;
  1291.                     if(!EscapeMap.TryGetValue(escaped[i], out c)) {
  1292.                         c = escaped[i]; // unknown escape sequences are literals
  1293.                     }
  1294.                     sb.Append(escaped.Substring(startIndex, i - startIndex - 1));
  1295.                     sb.Append(c);
  1296.                     startIndex = i + 1;
  1297.                     inEscape = false;
  1298.                 }
  1299.             }
  1300.             sb.Append(escaped.Substring(startIndex));
  1301.             return sb.ToString();
  1302.         }
  1303.     }
  1304.     #endregion // static utility functions
  1305.     #region public API
  1306.     /// <summary>if delegates are here, calls this code instead of executing a known a command</summary>
  1307.     private event DoAfterStringIsRead waitingToReadLine;
  1308.     /// <summary>If this is set, ignore the native command line functionality, and just do this</summary>
  1309.     public DoAfterStringIsRead onInput;
  1310.  
  1311.     /// <summary>what to do after a string is read.</summary>
  1312.     public delegate void DoAfterStringIsRead(string readFromUser);
  1313.     public delegate void DoAfterVisiblityChange();
  1314.     public static void SetText(string text) { Instance.setText(text); }
  1315.     /// <returns>The all text, including user input</returns>
  1316.     public string GetAllText() { return (_tmpInputField) ? GetRawText() : nonUserInput; }
  1317.     /// <param name="text">Text to add as output, also turning current user input into text output</param>
  1318.     public void AddText(string text) {
  1319.         // TODO clean up this function
  1320.         if(indexWherePromptWasPrintedRecently >= 0) {
  1321.             if(GetRawText().Length >= 0) {
  1322.                 //Debug.Log(indexWherePromptWasPrintedRecently+" vs "+GetRawText().Length);
  1323.                 if(indexWherePromptWasPrintedRecently < GetRawText().Length) {
  1324.                     EndUserInputIfNeeded();
  1325.                     setText(GetAllText().Substring(0, indexWherePromptWasPrintedRecently) + text);
  1326.                 } else {
  1327.                     EndUserInputIfNeeded();
  1328.                     setText(GetAllText() + text);
  1329.                 }
  1330.             } else {
  1331.                 setText(text);
  1332.             }
  1333.         } else {
  1334.             EndUserInputIfNeeded();
  1335.             setText(GetAllText() + text);
  1336.         }
  1337.         indexWherePromptWasPrintedRecently = -1;
  1338.     }
  1339.     /// <param name="line">line to add as output, also turning current user input into text output</param>
  1340.     public void println(string line) {
  1341.         AddText(line + "\n");
  1342.     }
  1343.     public void readLineAsync(DoAfterStringIsRead stringCallback) {
  1344.         if(!IsInteractive() && _tmpInputField != null) { SetInteractive(true); }
  1345.         waitingToReadLine += stringCallback;
  1346.     }
  1347.     public void getInputAsync(DoAfterStringIsRead stringCallback) { readLineAsync(stringCallback); }
  1348.     public static void GetInputAsync(DoAfterStringIsRead stringCallback) { Instance.readLineAsync(stringCallback); }
  1349.     public static void ReadLine(DoAfterStringIsRead stringCallback) { Instance.readLineAsync(stringCallback); }
  1350.     /// <summary>Instance.println(line)</summary>
  1351.     public static void Log(string line) { Instance.println(line); }
  1352.     public void log(string line) { println(line); }
  1353.     public void readLine(DoAfterStringIsRead stringCallback) { readLineAsync(stringCallback); }
  1354.     public string GetRawText() { return _tmpInputField.text; }
  1355.     // don't use this, use setText instead, if possible
  1356.     private void SetRawText(string s) {
  1357.         if(_tmpInputField != null) {
  1358.             _tmpInputField.text = s;
  1359.         }
  1360.     }
  1361.     public int GetCaretPosition() { return _tmpInputField.stringPosition; }
  1362.     public void SetCaretPosition(int pos) { _tmpInputField.stringPosition = pos; }
  1363.     #endregion // pubilc API
  1364.     #region Unity Editor interaction
  1365. #if UNITY_EDITOR
  1366.     private static Mesh _editorMesh = null; // one variable to enable better UI in the editor
  1367.  
  1368.     public List<Action> thingsToDoWhileEditorIsRunning = new List<Action>();
  1369.     void OnValidate() {
  1370.         thingsToDoWhileEditorIsRunning.Add(() => {
  1371.             Interactivity = interactivity;
  1372.             InterceptDebugLog = interceptDebugLog;
  1373.             TextMeshProFont = textMeshProFont;
  1374.         });
  1375.     }
  1376.  
  1377.     void OnDrawGizmos() {
  1378.         if(_editorMesh == null) {
  1379.             _editorMesh = new Mesh();
  1380.             _editorMesh.vertices = new Vector3[] { new Vector3(-.5f, .5f), new Vector3(.5f, .5f), new Vector3(-.5f, -.5f), new Vector3(.5f, -.5f) };
  1381.             _editorMesh.triangles = new int[] { 0, 1, 2, 3, 2, 1 };
  1382.             _editorMesh.RecalculateNormals();
  1383.             _editorMesh.RecalculateBounds();
  1384.         }
  1385.         Vector3 s = this.WorldSpaceSettings.screenSize;
  1386.         if(s == Vector3.zero) { s = new Vector3(Screen.width, Screen.height, 1); }
  1387.         s.Scale(transform.lossyScale);
  1388.         s *= WorldSpaceSettings.textScale;
  1389.         Color c = ColorSet.Background;
  1390.         Gizmos.color = c;
  1391.         if(!UnityEditor.EditorApplication.isPlaying) {
  1392.             Gizmos.DrawMesh(_editorMesh, transform.position, transform.rotation, s);
  1393.         }
  1394.         Transform t = transform;
  1395.         // calculate extents
  1396.         Vector3[] points = {(t.up*s.y/2 + t.right*s.x/-2),(t.up*s.y/2 + t.right*s.x/2),
  1397.             (t.up*s.y/-2 + t.right*s.x/2),(t.up*s.y/-2 + t.right*s.x/-2)};
  1398.         for(int i = 0; i < points.Length; ++i) { points[i] += t.position; }
  1399.         c.a = 1;
  1400.         Gizmos.color = c;
  1401.         for(int i = 0; i < points.Length; ++i) {
  1402.             Gizmos.DrawLine(points[i], points[(i + 1) % points.Length]);
  1403.         }
  1404.     }
  1405. #endif
  1406.     #endregion // Unity Editor interaction
  1407.     #region Enable/Disable
  1408.     [System.Serializable]
  1409.     public struct Callbacks {
  1410.         [Tooltip("When the command line goes into active editing. This may be useful to refresh info for the command line, or disable a 3D FPS controller.")]
  1411.         public UnityEngine.Events.UnityEvent whenThisActivates;
  1412.         [Tooltip("When the command line leaves active editing. This may be useful to re-enable a 3D FPS controller.")]
  1413.         public UnityEngine.Events.UnityEvent whenThisDeactivates;
  1414.         [Tooltip("When a command is executed. Check <code>RecentInstruction</code>")]
  1415.         public UnityEngine.Events.UnityEvent whenCommandRuns;
  1416.         public bool ignoreCallbacks;
  1417.     }
  1418.     [Tooltip("Recommended scripts to pair with the CmdLine: pastebin.com/FaT6i5yF\nwhenThisActivates:    StopPhysics.enablePhysics()\nwhenThisDeactivates: StopPhysics.disablePhysics()")]
  1419.     public Callbacks callbacks = new Callbacks();
  1420.     #endregion // Enable/Disable
  1421.     #region MonoBehaviour
  1422.     void Start() {
  1423.         if(_instance == null) { _instance = this; }
  1424.         showBottomWhenTextIsAdded = true;
  1425.         NeedToRefreshUserPrompt = true;
  1426.         // test code
  1427.         PopulateWithBasicCommands();
  1428.         SetInteractive(ActiveOnStart);
  1429.     }
  1430.     void Update() {
  1431. #if UNITY_EDITOR
  1432.         if(thingsToDoWhileEditorIsRunning.Count > 0) {
  1433.             thingsToDoWhileEditorIsRunning.ForEach(a => a());
  1434.             thingsToDoWhileEditorIsRunning.Clear();
  1435.         }
  1436. #endif
  1437.         if(Interactivity != InteractivityEnum.Disabled) {
  1438.             // toggle visibility based on key presses
  1439.             bool toggle = Input.GetKeyDown(IsInteractive() ? KeyToDeactivate : KeyToActivate);
  1440.             // or toggle visibility when 5 fingers touch
  1441.             if(Input.touches.Length == 5) {
  1442.                 if(!_togglingVisiblityWithMultitouch) {
  1443.                     toggle = true;
  1444.                     _togglingVisiblityWithMultitouch = true;
  1445.                 }
  1446.             } else {
  1447.                 _togglingVisiblityWithMultitouch = false;
  1448.             }
  1449.             if(toggle) {
  1450.                 if(!IsInteractive()) {
  1451.                     // check to see how clearly the user is looking at this CmdLine
  1452.                     if(_mainView.renderMode == RenderMode.ScreenSpaceOverlay) {
  1453.                         this.viewscore = 1;
  1454.                     } else {
  1455.                         Transform cameraTransform = Camera.main
  1456.                             ?Camera.main.transform:FindObjectOfType<Camera>().transform;
  1457.                         Vector3 lookPosition = cameraTransform.position;
  1458.                         Vector3 gaze = cameraTransform.forward;
  1459.                         Vector3 delta = transform.position - lookPosition;
  1460.                         float distFromCam = delta.magnitude;
  1461.                         float viewAlignment = Vector3.Dot(gaze, delta / distFromCam);
  1462.                         if(viewAlignment < 0) {
  1463.                             this.viewscore = -1;
  1464.                         } else {
  1465.                             this.viewscore = (1 - viewAlignment) * distFromCam;
  1466.                         }
  1467.                     }
  1468.                     if(currentlyActiveCmdLine == null
  1469.                         || (currentlyActiveCmdLine != null && (currentlyActiveCmdLine.viewscore < 0
  1470.                             || (this.viewscore >= 0 && this.viewscore <= currentlyActiveCmdLine.viewscore)))) {
  1471.                         SetInteractive(true);
  1472.                     }
  1473.                 } else {
  1474.                     SetInteractive(false);
  1475.                     this.viewscore = -1;
  1476.                 }
  1477.             }
  1478.             // stop trying to show the bottom if the user wants to scroll
  1479.             if(Input.GetAxis("Mouse ScrollWheel") != 0) {
  1480.                 showBottomWhenTextIsAdded = _tmpInputField.verticalScrollbar.value == 1;
  1481.             }
  1482.             if(showBottomWhenTextIsAdded) {
  1483.                 _tmpInputField.verticalScrollbar.value = 1;
  1484.             }
  1485.         }
  1486.         Instruction instruction = PopInstruction();
  1487. #if CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  1488.         if(bash == null) { bash = new BASH(); }
  1489.         if(bash.IsInitialized() && AllowSystemAccess
  1490.         && (instruction == null || instruction.IsUser(UserRawInput) || instruction.user == bash)) {
  1491.             bash.Update(instruction, this); // always update, since this also pushes the pipeline
  1492.         } else {
  1493. #endif
  1494.             // run any queued-up commands
  1495.             if(instruction != null) {
  1496.                 Run(instruction);
  1497.                 NeedToRefreshUserPrompt = true;
  1498.                 if(!callbacks.ignoreCallbacks && callbacks.whenCommandRuns != null) callbacks.whenCommandRuns.Invoke();
  1499.                 CalculateInvisibleSubstrings();
  1500.             }
  1501. #if CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  1502.         }
  1503. #endif
  1504.  
  1505.         if(Interactivity != InteractivityEnum.Disabled) {
  1506.             // make sure the cursor skips over invisible areas
  1507.             int CursorPosition = GetCaretPosition();
  1508.             int index = WhichInvisibleSubstring(GetCaretPosition());
  1509.             if(index >= 0) {
  1510.                 Substring sb = invisibleSubstrings[index];
  1511.                 if(CursorPosition >= sb.Middle){
  1512.                     CursorPosition = sb.index;
  1513.                     if(CursorPosition > 0) CursorPosition--;
  1514.                 } else {
  1515.                     CursorPosition = sb.Limit;
  1516.                 }
  1517.                 SetCaretPosition(CursorPosition);
  1518.             }
  1519.  
  1520.             if(NeedToRefreshUserPrompt && onInput == null
  1521. #if CONNECT_TO_REAL_COMMAND_LINE_TERMINAL
  1522.             && bash.IsProbablyIdle()
  1523. #endif
  1524.             && (waitingToReadLine == null || waitingToReadLine.GetInvocationList().Length == 0)) {
  1525.                 // in case of keyboard mashing...
  1526.                 if(GetUserInputLength() > 0) {
  1527.                     string userInput = GetUserInput();
  1528.                     SetText(nonUserInput); GetInputValidator().EndUserInput(true);
  1529.                     PrintPrompt(); GetInputValidator().AddUserInput(userInput);
  1530.                     nonUserInput = _tmpInputField.text.Substring(0, _tmpInputField.text.Length - userInput.Length);
  1531.                 } else { PrintPrompt(); }
  1532.                 NeedToRefreshUserPrompt = false;
  1533.             }
  1534.         }
  1535.         // if this is the active command line and it has not yet disabled user controls. done in update to stop many onStart and onStop calls from being invoked in series
  1536.         if(currentlyActiveCmdLine == this && disabledUserControls != this) {
  1537.             // if another command line disabled user controls
  1538.             if(disabledUserControls != null) {
  1539.                 // tell it to re-enable controls
  1540.                 if(!callbacks.ignoreCallbacks && callbacks.whenThisDeactivates != null) callbacks.whenThisDeactivates.Invoke();
  1541.             }
  1542.             disabledUserControls = this;
  1543.             if(!callbacks.ignoreCallbacks && callbacks.whenThisActivates != null) callbacks.whenThisActivates.Invoke();
  1544.         }
  1545.     }
  1546.     #endregion // MonoBehaviour
  1547. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement