diff --git a/src/UI/CSConsole/CSAutoCompleter.cs b/src/UI/CSConsole/CSAutoCompleter.cs
index dbf0ac7..fa5f084 100644
--- a/src/UI/CSConsole/CSAutoCompleter.cs
+++ b/src/UI/CSConsole/CSAutoCompleter.cs
@@ -52,12 +52,12 @@ namespace UnityExplorer.UI.CSConsole
return;
}
- // get the current composition string (from caret back to last delimiter or whitespace)
+ // get the current composition string (from caret back to last delimiter)
while (start > 0)
{
start--;
char c = InputField.Text[start];
- if (char.IsWhiteSpace(c) || delimiters.Contains(c))
+ if (delimiters.Contains(c))
{
start++;
break;
@@ -68,25 +68,25 @@ namespace UnityExplorer.UI.CSConsole
// Get MCS completions
string[] evaluatorCompletions = ConsoleController.Evaluator.GetCompletions(input, out string prefix);
-
- if (!string.IsNullOrEmpty(prefix) && evaluatorCompletions != null && evaluatorCompletions.Any())
+
+ if (evaluatorCompletions != null && evaluatorCompletions.Any())
{
suggestions.AddRange(from completion in evaluatorCompletions
- select new Suggestion($"{prefix}{completion}", completion));
+ select new Suggestion(GetHighlightString(prefix, completion), completion));
}
// Get manual keyword completions
foreach (var kw in KeywordLexer.keywords)
{
- if (kw.StartsWith(input))
+ if (kw.StartsWith(input) && kw.Length > input.Length)
{
+ if (!keywordHighlights.ContainsKey(kw))
+ keywordHighlights.Add(kw, $"{kw}");
+
string completion = kw.Substring(input.Length, kw.Length - input.Length);
-
- suggestions.Add(new Suggestion(
- $"{input}" +
- $"{completion}",
- completion));
+
+ suggestions.Add(new Suggestion(keywordHighlights[kw], completion));
}
}
@@ -100,5 +100,20 @@ namespace UnityExplorer.UI.CSConsole
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
}
+
+ private readonly Dictionary keywordHighlights = new Dictionary();
+
+ private readonly StringBuilder highlightBuilder = new StringBuilder();
+ private const string OPEN_HIGHLIGHT = "";
+
+ private string GetHighlightString(string prefix, string completion)
+ {
+ highlightBuilder.Clear();
+ highlightBuilder.Append(OPEN_HIGHLIGHT);
+ highlightBuilder.Append(prefix);
+ highlightBuilder.Append(SignatureHighlighter.CLOSE_COLOR);
+ highlightBuilder.Append(completion);
+ return highlightBuilder.ToString();
+ }
}
}
diff --git a/src/UI/CSConsole/ConsoleController.cs b/src/UI/CSConsole/ConsoleController.cs
index 9b786e5..6a1116b 100644
--- a/src/UI/CSConsole/ConsoleController.cs
+++ b/src/UI/CSConsole/ConsoleController.cs
@@ -38,9 +38,8 @@ namespace UnityExplorer.UI.CSConsole
{
"System",
"System.Linq",
- "System.Collections",
+ "System.Text",
"System.Collections.Generic",
- "System.Reflection",
"UnityEngine",
#if CPP
"UnhollowerBaseLib",
@@ -68,16 +67,19 @@ namespace UnityExplorer.UI.CSConsole
Lexer = new LexerBuilder();
Completer = new CSAutoCompleter();
+ SetupHelpInteraction();
+
Panel.OnInputChanged += OnInputChanged;
Panel.InputScroll.OnScroll += OnInputScrolled;
Panel.OnCompileClicked += Evaluate;
Panel.OnResetClicked += ResetConsole;
+ Panel.OnHelpDropdownChanged += HelpSelected;
Panel.OnAutoIndentToggled += OnToggleAutoIndent;
Panel.OnCtrlRToggled += OnToggleCtrlRShortcut;
Panel.OnSuggestionsToggled += OnToggleSuggestions;
-
}
+
#region UI Listeners and options
// TODO save
@@ -99,81 +101,6 @@ namespace UnityExplorer.UI.CSConsole
#endregion
- // Updating and event listeners
-
- private static bool settingAutoCompletion;
-
- private static void OnInputScrolled() => HighlightVisibleInput();
-
- // Invoked at most once per frame
- private static void OnInputChanged(string value)
- {
- if (!settingAutoCompletion && EnableSuggestions)
- Completer.CheckAutocompletes();
-
- if (!settingAutoCompletion && EnableAutoIndent)
- DoAutoIndent();
-
- HighlightVisibleInput();
- }
-
- public static void Update()
- {
- UpdateCaret(out bool caretMoved);
-
- if (!settingAutoCompletion && EnableSuggestions && caretMoved)
- {
- Completer.CheckAutocompletes();
- }
-
- if (EnableCtrlRShortcut
- && (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
- && InputManager.GetKeyDown(KeyCode.R))
- {
- Evaluate(Panel.Input.Text);
- }
- }
-
- private const int CSCONSOLE_LINEHEIGHT = 18;
-
- private static void UpdateCaret(out bool caretMoved)
- {
- int prevCaret = LastCaretPosition;
- caretMoved = false;
-
- if (Input.Component.isFocused)
- {
- LastCaretPosition = Input.Component.caretPosition;
- caretMoved = LastCaretPosition != prevCaret;
- }
-
- if (Input.Text.Length == 0)
- return;
-
- // If caret moved, ensure caret is visible in the viewport
- if (caretMoved)
- {
- var charInfo = Input.TextGenerator.characters[LastCaretPosition];
- var charTop = charInfo.cursorPos.y;
- var charBot = charTop - CSCONSOLE_LINEHEIGHT;
-
- var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
- var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
-
- float diff = 0f;
- if (charTop > viewportMin)
- diff = charTop - viewportMin;
- else if (charBot < viewportMax)
- diff = charBot - viewportMax;
-
- if (Math.Abs(diff) > 1)
- {
- var rect = Input.Rect;
- rect.anchoredPosition = new Vector2(rect.anchoredPosition.x, rect.anchoredPosition.y - diff);
- }
- }
- }
-
#region Evaluating
@@ -216,19 +143,43 @@ namespace UnityExplorer.UI.CSConsole
{
try
{
- Evaluator.Run(input);
+ // Try to "Compile" the code (tries to interpret it as REPL)
+ var evaluation = Evaluator.Compile(input);
+ if (evaluation != null)
+ {
+ // Valid REPL, we have a delegate to the evaluation.
+ try
+ {
+ object ret = null;
+ evaluation.Invoke(ref ret);
+ var result = ret?.ToString();
+ if (!string.IsNullOrEmpty(result))
+ ExplorerCore.Log($"Invoked REPL, result: {ret}");
+ else
+ ExplorerCore.Log($"Invoked REPL (no return value)");
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.LogWarning($"Exception invoking REPL: {ex}");
+ }
+ }
+ else
+ {
+ // The input was not recognized as an evaluation. Compile the code.
- string output = ScriptEvaluator._textWriter.ToString();
- var outputSplit = output.Split('\n');
- if (outputSplit.Length >= 2)
- output = outputSplit[outputSplit.Length - 2];
- evaluatorOutput.Clear();
+ Evaluator.Run(input);
- if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
- throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
+ string output = ScriptEvaluator._textWriter.ToString();
+ var outputSplit = output.Split('\n');
+ if (outputSplit.Length >= 2)
+ output = outputSplit[outputSplit.Length - 2];
+ evaluatorOutput.Clear();
- //if (!supressLog)
- // ExplorerCore.Log("Code executed successfully.");
+ if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
+ throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
+ else if (!supressLog)
+ ExplorerCore.Log($"Code compiled without errors.");
+ }
}
catch (FormatException fex)
{
@@ -245,6 +196,145 @@ namespace UnityExplorer.UI.CSConsole
#endregion
+ // Updating and event listeners
+
+ private static bool settingCaretCoroutine;
+
+ private static void OnInputScrolled() => HighlightVisibleInput();
+
+ private static string previousInput;
+
+ // Invoked at most once per frame
+ private static void OnInputChanged(string value)
+ {
+ // prevent escape wiping input
+ if (InputManager.GetKeyDown(KeyCode.Escape))
+ {
+ Input.Text = previousInput;
+
+ if (EnableSuggestions && AutoCompleteModal.CheckEscape(Completer))
+ OnAutocompleteEscaped();
+
+ return;
+ }
+
+ previousInput = value;
+
+ if (EnableSuggestions && AutoCompleteModal.CheckEnter(Completer))
+ {
+ OnAutocompleteEnter();
+ }
+ else if (!settingCaretCoroutine)
+ {
+ if (EnableSuggestions)
+ Completer.CheckAutocompletes();
+
+ if (EnableAutoIndent)
+ DoAutoIndent();
+ }
+
+ HighlightVisibleInput();
+ }
+
+ public static void Update()
+ {
+ UpdateCaret(out bool caretMoved);
+
+ if (!settingCaretCoroutine && EnableSuggestions && AutoCompleteModal.CheckEscape(Completer))
+ {
+ OnAutocompleteEscaped();
+ return;
+ }
+
+ if (!settingCaretCoroutine && EnableSuggestions && caretMoved)
+ {
+ Completer.CheckAutocompletes();
+ }
+
+ if (EnableCtrlRShortcut
+ && (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
+ && InputManager.GetKeyDown(KeyCode.R))
+ {
+ Evaluate(Panel.Input.Text);
+ }
+ }
+
+ private const int CSCONSOLE_LINEHEIGHT = 18;
+
+ private static void UpdateCaret(out bool caretMoved)
+ {
+ int prevCaret = LastCaretPosition;
+ caretMoved = false;
+
+ // Override up/down arrow movement when autocompleting
+ if (EnableSuggestions && AutoCompleteModal.CheckNavigation(Completer))
+ {
+ Input.Component.caretPosition = LastCaretPosition;
+ return;
+ }
+
+ if (Input.Component.isFocused)
+ {
+ LastCaretPosition = Input.Component.caretPosition;
+ caretMoved = LastCaretPosition != prevCaret;
+ }
+
+ if (Input.Text.Length == 0)
+ return;
+
+ // If caret moved, ensure caret is visible in the viewport
+ if (caretMoved)
+ {
+ var charInfo = Input.TextGenerator.characters[LastCaretPosition];
+ var charTop = charInfo.cursorPos.y;
+ var charBot = charTop - CSCONSOLE_LINEHEIGHT;
+
+ var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
+ var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
+
+ float diff = 0f;
+ if (charTop > viewportMin)
+ diff = charTop - viewportMin;
+ else if (charBot < viewportMax)
+ diff = charBot - viewportMax;
+
+ if (Math.Abs(diff) > 1)
+ {
+ var rect = Input.Rect;
+ rect.anchoredPosition = new Vector2(rect.anchoredPosition.x, rect.anchoredPosition.y - diff);
+ }
+ }
+ }
+
+ private static void SetCaretPosition(int caretPosition)
+ {
+ settingCaretCoroutine = true;
+ RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaretCoro(caretPosition));
+ }
+
+ private static IEnumerator SetAutocompleteCaretCoro(int caretPosition)
+ {
+ var color = Input.Component.selectionColor;
+ color.a = 0f;
+ Input.Component.selectionColor = color;
+ EventSystem.current.SetSelectedGameObject(null, null);
+ yield return null;
+
+ EventSystem.current.SetSelectedGameObject(Input.UIRoot, null);
+ Input.Component.Select();
+ yield return null;
+
+ Input.Component.caretPosition = caretPosition;
+ Input.Component.selectionFocusPosition = caretPosition;
+ LastCaretPosition = Input.Component.caretPosition;
+
+ color.a = defaultInputFieldAlpha;
+ Input.Component.selectionColor = color;
+
+ settingCaretCoroutine = false;
+ }
+
+
#region Lexer Highlighting
private static void HighlightVisibleInput()
@@ -295,31 +385,28 @@ namespace UnityExplorer.UI.CSConsole
public static void InsertSuggestionAtCaret(string suggestion)
{
- settingAutoCompletion = true;
+ settingCaretCoroutine = true;
Input.Text = Input.Text.Insert(LastCaretPosition, suggestion);
- RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaret(LastCaretPosition + suggestion.Length));
+ SetCaretPosition(LastCaretPosition + suggestion.Length);
LastCaretPosition = Input.Component.caretPosition;
}
- private static IEnumerator SetAutocompleteCaret(int caretPosition)
+ private static void OnAutocompleteEnter()
{
- var color = Input.Component.selectionColor;
- color.a = 0f;
- Input.Component.selectionColor = color;
- yield return null;
+ // Remove the new line
+ int lastIdx = Input.Component.caretPosition - 1;
+ Input.Text = Input.Text.Remove(lastIdx, 1);
- EventSystem.current.SetSelectedGameObject(Panel.Input.UIRoot, null);
- yield return null;
+ // Use the selected suggestion
+ Input.Component.caretPosition = LastCaretPosition;
+ Completer.OnSuggestionClicked(AutoCompleteModal.SelectedSuggestion);
+ }
- Input.Component.caretPosition = caretPosition;
- Input.Component.selectionFocusPosition = caretPosition;
- LastCaretPosition = Input.Component.caretPosition;
-
- color.a = defaultInputFieldAlpha;
- Input.Component.selectionColor = color;
-
- settingAutoCompletion = false;
+ private static void OnAutocompleteEscaped()
+ {
+ AutoCompleteModal.Instance.ReleaseOwnership(Completer);
+ SetCaretPosition(LastCaretPosition);
}
@@ -359,6 +446,104 @@ namespace UnityExplorer.UI.CSConsole
#endregion
+ #region "Help" interaction
+ private static readonly Dictionary helpDict = new Dictionary();
+
+ public static void SetupHelpInteraction()
+ {
+ var drop = Panel.HelpDropdown;
+
+ helpDict.Add("Help", "");
+ helpDict.Add("Usings", HELP_USINGS);
+ helpDict.Add("REPL", HELP_REPL);
+ helpDict.Add("Classes", HELP_CLASSES);
+ helpDict.Add("Coroutines", HELP_COROUTINES);
+
+ foreach (var opt in helpDict)
+ drop.options.Add(new Dropdown.OptionData(opt.Key));
+ }
+
+ public static void HelpSelected(int index)
+ {
+ if (index == 0)
+ return;
+
+ var helpText = helpDict.ElementAt(index);
+
+ Input.Text = helpText.Value;
+
+ Panel.HelpDropdown.value = 0;
+ }
+
+
+ internal const string STARTUP_TEXT = @"// Welcome to the UnityExplorer C# Console!
+// It is recommended to use the Log panel (or a console log window) while using this tool.
+// Use the Help dropdown to see detailed examples of how to use the console.";
+
+ internal const string HELP_USINGS = @"// To add a using directive, simply compile it like you would in your IDE:
+using UnityEngine.UI;
+
+// To see your current usings, evaluate ""GetUsing();"" as REPL. You cannot do this while adding usings.
+
+// To reset usings to default, press the Reset button.";
+
+ internal const string HELP_REPL = @"/* REPL (Read-Evaluate-Print-Loop) is a way to execute code immediately.
+ * REPL code cannot contain any using directives or classes.
+ * The return value of the last line of your REPL will be printed to the log.
+ * Variables defined in REPL will exist until you Reset the console.
+*/
+
+// eg: This code would print 'Hello, World!', and then print 6 as the return value.
+Log(""Hello, world!"");
+var x = 5;
+++x;
+
+/* The following helpers are available in REPL mode:
+ * CurrentTarget; - System.Object, the target of the active Inspector tab
+ * AllTargets; - System.Object[], the targets of all Inspector tabs
+ * Log(obj); - prints a message to the console log
+ * Inspect(obj); - inspect the object with the Inspector
+ * Inspect(someType); - inspect a Type with static reflection
+ * Start(enumerator); - starts the IEnumerator as a Coroutine
+ * GetUsing(); - prints the current using directives to the console log
+ * GetVars(); - prints the names and values of the REPL variables you have defined
+ * GetClasses(); - prints the names and members of the classes you have defined
+*/";
+
+ internal const string HELP_CLASSES = @"// Classes you compile will exist until the application closes.
+// You can soft-overwrite a class by compiling it again with the same name. The old class will still technically exist in memory.
+
+// Compiled classes can be accessed from both inside and outside this console.
+// Note: in IL2CPP, injecting these classes with ClassInjector may crash the game!
+
+public class HelloWorld
+{
+ public static void Main()
+ {
+ UnityExplorer.ExplorerCore.Log(""Hello, world!"");
+ }
+}
+
+// In REPL, you could call the example method above with ""HelloWorld.Main();""
+// Note: The compiler does not allow you to run REPL code and define classes at the same time.";
+
+ internal const string HELP_COROUTINES = @"// To start a Coroutine, you can use an existing IEnumerator or define one yourself.
+// You can start Coroutines from REPL by using ""Start(enumerator);""
+
+// To define a coroutine, for example:
+public class MyCoros
+{
+ public static IEnumerator Coro()
+ {
+ yield return null;
+ UnityExplorer.ExplorerCore.Log(""Hello, world after one frame!"");
+ }
+}
+// To run this Coroutine in REPL, it would look like ""Start(MyCoros.Coro());""
+// Note: You cannot define classes and run REPL code at the same time!
+";
+
+ #endregion
}
}
diff --git a/src/UI/CSConsole/LexerBuilder.cs b/src/UI/CSConsole/LexerBuilder.cs
index b4443f0..e8eb5e0 100644
--- a/src/UI/CSConsole/LexerBuilder.cs
+++ b/src/UI/CSConsole/LexerBuilder.cs
@@ -115,6 +115,13 @@ namespace UnityExplorer.UI.CSConsole
lastUnhighlighted = match.endIndex + 1;
}
+ // Append trailing unhighlighted input
+ while (lastUnhighlighted <= endIdx)
+ {
+ sb.Append(input[lastUnhighlighted]);
+ lastUnhighlighted++;
+ }
+
return sb.ToString();
}
diff --git a/src/UI/CSConsole/ScriptInteraction.cs b/src/UI/CSConsole/ScriptInteraction.cs
index 52c77e9..340a955 100644
--- a/src/UI/CSConsole/ScriptInteraction.cs
+++ b/src/UI/CSConsole/ScriptInteraction.cs
@@ -7,33 +7,16 @@ using System.Linq;
using UnityExplorer.Core.Runtime;
using System.Text;
+/*
+ Welcome to the UnityExplorer C# Console!
+ Use the Help dropdown to see detailed examples of how to use this console.
+ To see your output, use the Log panel or a Console Log window.
+*/
+
namespace UnityExplorer.UI.CSConsole
{
public class ScriptInteraction : InteractiveBase
{
- internal const string STARTUP_TEXT = @"// Compile a using directive to add it to the console (until Reset)
-using SomeNamespace;
-
-// Compile a C# class and it will exist until Reset
-public class SomeClass {
- public static void SomeMethod() {
- }
-}
-
-// If not compiling any usings or classes, the code will run immediately (REPL).
-// Variables you define in REPL mode will also exist until Reset.
-// In REPL context, the following helpers are available:
-
-* System.Object CurrentTarget - the target of the active Inspector tab
-* System.Object[] AllTargets - an array containing the targets of all Inspector tabs
-* void Log(""message"") - prints a message to the console log
-* void Inspect(someObject) - inspect an instance, eg. Inspect(Camera.main);
-* void Inspect(typeof(SomeClass)) - inspect a Class with static reflection
-* void StartCoroutine(ienumerator) - start the IEnumerator as a Coroutine
-* void GetUsing() - prints the current using directives to the console log
-* void GetVars() - prints the variables you have defined and their current values
-* void GetClasses() - prints the names of the classes you have defined, and their members";
-
public static void Log(object message)
{
ExplorerCore.Log(message);
@@ -41,7 +24,7 @@ using SomeNamespace;
public static object CurrentTarget => InspectorManager.ActiveInspector?.Target;
- public static object[] AllTargets() => InspectorManager.Inspectors.Select(it => it.Target).ToArray();
+ public static object[] AllTargets => InspectorManager.Inspectors.Select(it => it.Target).ToArray();
public static void Inspect(object obj)
{
@@ -53,7 +36,7 @@ using SomeNamespace;
InspectorManager.Inspect(type);
}
- public static void StartCoroutine(IEnumerator ienumerator)
+ public static void Start(IEnumerator ienumerator)
{
RuntimeProvider.Instance.StartCoroutine(ienumerator);
}
@@ -65,7 +48,11 @@ using SomeNamespace;
public static void GetVars()
{
- Log(Evaluator.GetVars());
+ var vars = Evaluator.GetVars()?.Trim();
+ if (string.IsNullOrEmpty(vars))
+ ExplorerCore.LogWarning("No variables seem to be defined!");
+ else
+ Log(vars);
}
public static void GetClasses()
diff --git a/src/UI/Panels/CSConsolePanel.cs b/src/UI/Panels/CSConsolePanel.cs
index 944ae25..bbadc66 100644
--- a/src/UI/Panels/CSConsolePanel.cs
+++ b/src/UI/Panels/CSConsolePanel.cs
@@ -15,7 +15,7 @@ namespace UnityExplorer.UI.Panels
{
public override string Name => "C# Console";
public override UIManager.Panels PanelType => UIManager.Panels.CSConsole;
- public override int MinWidth => 750;
+ public override int MinWidth => 740;
public override int MinHeight => 300;
public InputFieldScroller InputScroll { get; private set; }
@@ -23,10 +23,13 @@ namespace UnityExplorer.UI.Panels
public Text InputText { get; private set; }
public Text HighlightText { get; private set; }
- public Action OnInputChanged;
+ public Dropdown HelpDropdown { get; private set; }
+ // events
+ public Action OnInputChanged;
public Action OnResetClicked;
public Action OnCompileClicked;
+ public Action OnHelpDropdownChanged;
public Action OnCtrlRToggled;
public Action OnSuggestionsToggled;
public Action OnAutoIndentToggled;
@@ -74,6 +77,25 @@ namespace UnityExplorer.UI.Panels
default, TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(toolsRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
+ // Buttons
+
+ var compileButton = UIFactory.CreateButton(toolsRow, "CompileButton", "Compile", new Color(0.33f, 0.5f, 0.33f));
+ UIFactory.SetLayoutElement(compileButton.Component.gameObject, minHeight: 28, minWidth: 130, flexibleHeight: 0);
+ compileButton.ButtonText.fontSize = 15;
+ compileButton.OnClick += () => { OnCompileClicked?.Invoke(); };
+
+ var resetButton = UIFactory.CreateButton(toolsRow, "ResetButton", "Reset", new Color(0.33f, 0.33f, 0.33f));
+ UIFactory.SetLayoutElement(resetButton.Component.gameObject, minHeight: 28, minWidth: 80, flexibleHeight: 0);
+ resetButton.ButtonText.fontSize = 15;
+ resetButton.OnClick += () => { OnResetClicked?.Invoke(); };
+
+ // Help dropdown
+
+ var helpDrop = UIFactory.CreateDropdown(toolsRow, out var dropdown, "Help", 14, null);
+ UIFactory.SetLayoutElement(helpDrop, minHeight: 25, minWidth: 100);
+ HelpDropdown = dropdown;
+ HelpDropdown.onValueChanged.AddListener((int val) => { this.OnHelpDropdownChanged?.Invoke(val); });
+
// Enable Ctrl+R toggle
var ctrlRToggleObj = UIFactory.CreateToggle(toolsRow, "CtrlRToggle", out var CtrlRToggle, out Text ctrlRToggleText);
@@ -93,36 +115,26 @@ namespace UnityExplorer.UI.Panels
// Enable Auto-indent toggle
var autoIndentToggleObj = UIFactory.CreateToggle(toolsRow, "IndentToggle", out var AutoIndentToggle, out Text autoIndentToggleText);
- UIFactory.SetLayoutElement(autoIndentToggleObj, minWidth: 180, flexibleWidth: 0, minHeight: 25);
+ UIFactory.SetLayoutElement(autoIndentToggleObj, minWidth: 120, flexibleWidth: 0, minHeight: 25);
autoIndentToggleText.alignment = TextAnchor.UpperLeft;
autoIndentToggleText.text = "Auto-indent";
AutoIndentToggle.onValueChanged.AddListener((bool val) => { OnAutoIndentToggled?.Invoke(val); });
- // Buttons
-
- var resetButton = UIFactory.CreateButton(toolsRow, "ResetButton", "Reset", new Color(0.33f, 0.33f, 0.33f));
- UIFactory.SetLayoutElement(resetButton.Component.gameObject, minHeight: 28, minWidth: 80, flexibleHeight: 0);
- resetButton.ButtonText.fontSize = 15;
- resetButton.OnClick += OnResetClicked;
-
- var compileButton = UIFactory.CreateButton(toolsRow, "CompileButton", "Compile", new Color(0.33f, 0.5f, 0.33f));
- UIFactory.SetLayoutElement(compileButton.Component.gameObject, minHeight: 28, minWidth: 130, flexibleHeight: 0);
- compileButton.ButtonText.fontSize = 15;
- compileButton.OnClick += OnCompileClicked;
-
// Console Input
int fontSize = 16;
- var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", ScriptInteraction.STARTUP_TEXT, out var inputScroller, fontSize);
+ var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", ConsoleController.STARTUP_TEXT, out var inputScroller, fontSize);
InputScroll = inputScroller;
ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a;
Input.OnValueChanged += InvokeOnValueChanged;
InputText = Input.Component.textComponent;
InputText.supportRichText = false;
- InputText.color = Color.white;
Input.PlaceholderText.fontSize = fontSize;
+ InputText.color = Color.clear;
+ Input.Component.customCaretColor = true;
+ Input.Component.caretColor = Color.white;
// Lexer highlight text overlay
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", InputText.gameObject);
@@ -134,7 +146,7 @@ namespace UnityExplorer.UI.Panels
highlightTextRect.offsetMax = Vector2.zero;
HighlightText = highlightTextObj.AddComponent();
- HighlightText.color = Color.clear;
+ HighlightText.color = Color.white;
HighlightText.supportRichText = true;
HighlightText.fontSize = fontSize;
diff --git a/src/UI/Panels/LogPanel.cs b/src/UI/Panels/LogPanel.cs
index 66b7a7c..1e30027 100644
--- a/src/UI/Panels/LogPanel.cs
+++ b/src/UI/Panels/LogPanel.cs
@@ -80,7 +80,7 @@ namespace UnityExplorer.UI.Panels
CurrentStreamPath = Path.Combine(path, fileName);
- File.WriteAllLines(CurrentStreamPath, Logs.Select(it => it.message));
+ File.WriteAllLines(CurrentStreamPath, Logs.Select(it => it.message).ToArray());
}
// Logging
diff --git a/src/UI/Widgets/AutoComplete/AutoCompleteModal.cs b/src/UI/Widgets/AutoComplete/AutoCompleteModal.cs
index 439bad1..011b2a3 100644
--- a/src/UI/Widgets/AutoComplete/AutoCompleteModal.cs
+++ b/src/UI/Widgets/AutoComplete/AutoCompleteModal.cs
@@ -29,12 +29,17 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
public override bool ShouldSaveActiveState => false;
public override bool NavButtonWanted => false;
- public ISuggestionProvider CurrentHandler { get; private set; }
+ public static ISuggestionProvider CurrentHandler { get; private set; }
- public ButtonListSource dataHandler;
- public ScrollPool scrollPool;
+ public static ButtonListSource dataHandler;
+ public static ScrollPool scrollPool;
- private List suggestions = new List();
+ private static List Suggestions = new List();
+ private static int SelectedIndex = 0;
+
+ public static Suggestion SelectedSuggestion => Suggestions[SelectedIndex];
+
+ public static bool Suggesting(ISuggestionProvider handler) => CurrentHandler == handler && Instance.UIRoot.activeSelf;
public AutoCompleteModal()
{
@@ -42,47 +47,6 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
OnClickedOutsidePanels += AutoCompleter_OnClickedOutsidePanels;
}
- private void AutoCompleter_OnClickedOutsidePanels()
- {
- if (!this.UIRoot || !this.UIRoot.activeInHierarchy)
- return;
-
- if (CurrentHandler != null)
- ReleaseOwnership(CurrentHandler);
- else
- UIRoot.SetActive(false);
- }
-
- private void UIPanel_OnPanelsReordered()
- {
- if (!this.UIRoot || !this.UIRoot.activeInHierarchy)
- return;
-
- if (this.UIRoot.transform.GetSiblingIndex() != UIManager.PanelHolder.transform.childCount - 1)
- {
- if (CurrentHandler != null)
- ReleaseOwnership(CurrentHandler);
- else
- UIRoot.SetActive(false);
- }
- }
-
- public override void Update()
- {
- if (!UIRoot || !UIRoot.activeSelf)
- return;
-
- if (suggestions.Any() && CurrentHandler != null)
- {
- if (!CurrentHandler.InputField.UIRoot.activeInHierarchy)
- ReleaseOwnership(CurrentHandler);
- else
- {
- UpdatePosition();
- }
- }
- }
-
public void TakeOwnership(ISuggestionProvider provider)
{
CurrentHandler = provider;
@@ -100,43 +64,170 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
}
}
- private List GetEntries() => suggestions;
-
- private bool ShouldDisplay(Suggestion data, string filter) => true;
-
- public void SetSuggestions(IEnumerable collection)
+ public void SetSuggestions(IEnumerable suggestions)
{
- suggestions = collection as List ?? collection.ToList();
+ Suggestions = suggestions as List ?? suggestions.ToList();
+ SelectedIndex = 0;
- if (!suggestions.Any())
- UIRoot.SetActive(false);
+ if (!Suggestions.Any())
+ base.UIRoot.SetActive(false);
else
{
- UIRoot.SetActive(true);
- UIRoot.transform.SetAsLastSibling();
+ base.UIRoot.SetActive(true);
+ base.UIRoot.transform.SetAsLastSibling();
dataHandler.RefreshData();
scrollPool.Refresh(true, true);
}
}
+ private static float timeOfLastNavHold = -1f;
+
+ ///
+ /// Returns true if the AutoCompleteModal used the navigation input, false if not.
+ /// The navigation inputs are Control+Up/Down, and Control+Enter.
+ ///
+ public static bool CheckNavigation(ISuggestionProvider handler)
+ {
+ if (!Suggesting(handler))
+ return false;
+
+ if (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
+ {
+ bool up = InputManager.GetKey(KeyCode.UpArrow);
+ bool down = InputManager.GetKey(KeyCode.DownArrow);
+
+ if (up || down)
+ {
+ if (up)
+ {
+ if (InputManager.GetKeyDown(KeyCode.UpArrow))
+ {
+ SetSelectedSuggestion(SelectedIndex - 1);
+ timeOfLastNavHold = Time.realtimeSinceStartup + 0.3f;
+ }
+ else if (timeOfLastNavHold.OccuredEarlierThan(0.05f))
+ {
+ SetSelectedSuggestion(SelectedIndex - 1);
+ timeOfLastNavHold = Time.realtimeSinceStartup;
+ }
+ }
+ else
+ {
+ if (InputManager.GetKeyDown(KeyCode.DownArrow))
+ {
+ SetSelectedSuggestion(SelectedIndex + 1);
+ timeOfLastNavHold = Time.realtimeSinceStartup + 0.3f;
+ }
+ else if (timeOfLastNavHold.OccuredEarlierThan(0.05f))
+ {
+ SetSelectedSuggestion(SelectedIndex + 1);
+ timeOfLastNavHold = Time.realtimeSinceStartup;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ return !timeOfLastNavHold.OccuredEarlierThan(0.2f);
+ }
+
+ public static bool CheckEnter(ISuggestionProvider handler)
+ {
+ return Suggesting(handler) && InputManager.GetKeyDown(KeyCode.Return);
+ }
+
+ public static bool CheckEscape(ISuggestionProvider handler)
+ {
+ return Suggesting(handler) && InputManager.GetKeyDown(KeyCode.Escape);
+ }
+
+ private static void SetSelectedSuggestion(int index)
+ {
+ if (index < 0 || index >= Suggestions.Count)
+ return;
+
+ SelectedIndex = index;
+ scrollPool.Refresh(true, false);
+ }
+
+ // Internal update
+
+ public override void Update()
+ {
+ if (!UIRoot || !UIRoot.activeSelf)
+ return;
+
+ if (Suggestions.Any() && CurrentHandler != null)
+ {
+ if (!CurrentHandler.InputField.UIRoot.activeInHierarchy)
+ ReleaseOwnership(CurrentHandler);
+ else
+ {
+ UpdatePosition();
+ }
+ }
+ }
+
+ // Setting autocomplete cell buttons
+
+ private readonly Color selectedSuggestionColor = new Color(46/255f, 54/255f, 53/255f);
+ private readonly Color inactiveSuggestionColor = new Color(0.11f, 0.11f, 0.11f);
+
+ private List GetEntries() => Suggestions;
+
+ private bool ShouldDisplay(Suggestion data, string filter) => true;
+
private void OnCellClicked(int dataIndex)
{
- var suggestion = suggestions[dataIndex];
+ var suggestion = Suggestions[dataIndex];
CurrentHandler.OnSuggestionClicked(suggestion);
}
+ private bool setFirstCell;
+
private void SetCell(ButtonCell cell, int index)
{
- if (index < 0 || index >= suggestions.Count)
+ if (index < 0 || index >= Suggestions.Count)
{
cell.Disable();
return;
}
- var suggestion = suggestions[index];
+ var suggestion = Suggestions[index];
cell.Button.ButtonText.text = suggestion.DisplayText;
+
+ if (index == SelectedIndex && setFirstCell)
+ {
+ if (cell.Rect.MinY() > scrollPool.Viewport.MinY())
+ {
+ // cell is too far down
+ var diff = cell.Rect.MinY() - scrollPool.Viewport.MinY();
+ var pos = scrollPool.Content.anchoredPosition;
+ pos.y -= diff;
+ scrollPool.Content.anchoredPosition = pos;
+ }
+ else if (cell.Rect.MaxY() < scrollPool.Viewport.MaxY())
+ {
+ // cell is too far up
+ var diff = cell.Rect.MaxY() - scrollPool.Viewport.MaxY();
+ var pos = scrollPool.Content.anchoredPosition;
+ pos.y -= diff;
+ scrollPool.Content.anchoredPosition = pos;
+ }
+
+ RuntimeProvider.Instance.SetColorBlock(cell.Button.Component, selectedSuggestionColor);
+ }
+ else
+ RuntimeProvider.Instance.SetColorBlock(cell.Button.Component, inactiveSuggestionColor);
+
+ setFirstCell = true;
}
+ // Updating panel position
+
private int lastCaretPosition;
private Vector3 lastInputPosition;
@@ -175,6 +266,35 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
this.Dragger.OnEndResize();
}
+ // Event listeners for panel
+
+ private void AutoCompleter_OnClickedOutsidePanels()
+ {
+ if (!this.UIRoot || !this.UIRoot.activeInHierarchy)
+ return;
+
+ if (CurrentHandler != null)
+ ReleaseOwnership(CurrentHandler);
+ else
+ UIRoot.SetActive(false);
+ }
+
+ private void UIPanel_OnPanelsReordered()
+ {
+ if (!this.UIRoot || !this.UIRoot.activeInHierarchy)
+ return;
+
+ if (this.UIRoot.transform.GetSiblingIndex() != UIManager.PanelHolder.transform.childCount - 1)
+ {
+ if (CurrentHandler != null)
+ ReleaseOwnership(CurrentHandler);
+ else
+ UIRoot.SetActive(false);
+ }
+ }
+
+ // UI Construction
+
protected internal override void DoSetDefaultPosAndAnchors()
{
var mainRect = uiRoot.GetComponent();
@@ -190,9 +310,13 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
scrollPool = UIFactory.CreateScrollPool(this.content, "AutoCompleter", out GameObject scrollObj, out GameObject scrollContent);
scrollPool.Initialize(dataHandler);
UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999);
-
UIFactory.SetLayoutGroup(scrollContent, true, false, true, false);
+ var bottomRow = UIFactory.CreateHorizontalGroup(this.content, "BottomRow", true, true, true, true, 0, new Vector4(2, 2, 2, 2));
+ UIFactory.SetLayoutElement(bottomRow, minHeight: 20, flexibleWidth: 9999);
+ UIFactory.CreateLabel(bottomRow, "HelpText", "Control+Up/Down to select, Enter to use, Esc to hide",
+ TextAnchor.MiddleLeft, Color.grey, false, 13);
+
UIRoot.SetActive(false);
}
diff --git a/src/UI/Widgets/AutoComplete/Suggestion.cs b/src/UI/Widgets/AutoComplete/Suggestion.cs
index 55e7190..7b67c5f 100644
--- a/src/UI/Widgets/AutoComplete/Suggestion.cs
+++ b/src/UI/Widgets/AutoComplete/Suggestion.cs
@@ -11,17 +11,12 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
{
public readonly string DisplayText;
public readonly string UnderlyingValue;
- //public int InsertIndex;
- //public readonly string Prefix;
- //public readonly string Addition;
- //public string Full => Prefix + Addition;
+ public string Combined => DisplayText + UnderlyingValue;
- public Suggestion(string displayText, /* string prefix, string addition, */ string underlyingValue)
+ public Suggestion(string displayText, string underlyingValue)
{
DisplayText = displayText;
- //Addition = addition;
- //Prefix = prefix;
UnderlyingValue = underlyingValue;
}
}