From 1d24af5666e6db9ae84e5c13bd89c48fbb42a9f9 Mon Sep 17 00:00:00 2001 From: Sinai Date: Mon, 10 May 2021 21:07:27 +1000 Subject: [PATCH] Only lexer-highlight what is shown in CS console (fix max vert overflow) --- src/Core/Tests/TestClass.cs | 4 + src/UI/CSConsole/CSConsole.cs | 182 ++++++++++++++++++--------- src/UI/IValues/InteractiveString.cs | 2 +- src/UI/Models/InputFieldRef.cs | 8 +- src/UI/Panels/CSConsolePanel.cs | 36 +++--- src/UI/UIManager.cs | 1 + src/UI/Widgets/InputFieldScroller.cs | 42 +++++-- 7 files changed, 182 insertions(+), 93 deletions(-) diff --git a/src/Core/Tests/TestClass.cs b/src/Core/Tests/TestClass.cs index 5f83fb3..521738e 100644 --- a/src/Core/Tests/TestClass.cs +++ b/src/Core/Tests/TestClass.cs @@ -6,6 +6,7 @@ using System.Text; using UnityEngine; using UnityExplorer.UI.IValues; using System.Reflection; +using UnityExplorer.UI; #if CPP using UnhollowerRuntimeLib; using UnhollowerBaseLib; @@ -70,6 +71,9 @@ namespace UnityExplorer.Tests public static TestValueStruct AATestStruct; + public static string AAATooLongString = new string('#', UIManager.MAX_INPUTFIELD_CHARS + 2); + public static string AAAMaxString = new string('@', UIManager.MAX_INPUTFIELD_CHARS); + public static TestEnum AATestEnumOne = TestEnum.Neg50; public static TestEnum2 AATestEnumTwo = TestEnum2.Max; public static TestFlags AATestFlags = TestFlags.Thirteen; diff --git a/src/UI/CSConsole/CSConsole.cs b/src/UI/CSConsole/CSConsole.cs index cafe76c..c290282 100644 --- a/src/UI/CSConsole/CSConsole.cs +++ b/src/UI/CSConsole/CSConsole.cs @@ -61,7 +61,7 @@ The following helper methods are available: private static HashSet usingDirectives; private static CSConsolePanel Panel => UIManager.CSharpConsole; - private static InputField InputField => Panel.InputField.InputField; + private static InputFieldRef Input => Panel.Input; // Todo save as config? public static bool EnableCtrlRShortcut { get; private set; } = true; @@ -78,7 +78,8 @@ The following helper methods are available: Evaluator.Compile("0 == 0"); Panel.OnInputChanged += OnConsoleInputChanged; - // TODO other panel listeners + Panel.InputScroll.OnScroll += ForceOnContentChange; + // TODO other panel listeners (buttons, etc) } catch (Exception ex) @@ -87,6 +88,123 @@ The following helper methods are available: } } + // Updating and event listeners + + private static readonly KeyCode[] onFocusKeys = + { + KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow, + KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow + }; + + public static void Update() + { + if (EnableCtrlRShortcut) + { + if ((InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl)) + && InputManager.GetKeyDown(KeyCode.R)) + { + var text = Panel.Input.Text.Trim(); + if (!string.IsNullOrEmpty(text)) + { + Evaluate(text); + return; + } + } + } + + //if (EnableAutoIndent && InputManager.GetKeyDown(KeyCode.Return)) + // DoAutoIndent(); + + //if (EnableAutocompletes && InputField.isFocused) + //{ + // if (InputManager.GetMouseButton(0) || onFocusKeys.Any(it => InputManager.GetKeyDown(it))) + // UpdateAutocompletes(); + //} + } + + private static void ForceOnContentChange() + { + OnConsoleInputChanged(Input.Text); + } + + // Invoked at most once per frame + private static void OnConsoleInputChanged(string value) + { + // todo auto indent? or only on enter? + + // syntax highlight + LexerHighlightAndSet(value); + + + // todo update auto completes + // ... + } + + private static void LexerHighlightAndSet(string value) + { + int startLine = 0; + int endLine = Input.TextGenerator.lineCount - 1; + + if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height) + { + // Not all text is displayed. + // Only syntax highlight what we need to. + + int topLine = -1; + int bottomLine = -1; + var half = Input.Rect.rect.height * 0.5f; + + var top = Input.Rect.rect.height - Input.Rect.anchoredPosition.y; + var bottom = top - Panel.InputScroll.ViewportRect.rect.height; + + for (int i = 0; i < Input.TextGenerator.lineCount; i++) + { + var line = Input.TextGenerator.lines[i]; + var pos = line.topY + half; + + if (topLine == -1 && pos <= top) + topLine = i; + + if ((pos - line.height) >= bottom) + bottomLine = i; + } + + startLine = Math.Max(0, topLine - 1); + endLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1); + } + + int startIdx = Input.TextGenerator.lines[startLine].startCharIdx; + int endIdx; + if (endLine >= Input.TextGenerator.lineCount - 1) + endIdx = value.Length - 1; + else + endIdx = Math.Min(value.Length - 1, Input.TextGenerator.lines[endLine + 1].startCharIdx); + + var sb = new StringBuilder(); + for (int i = 0; i < startLine; i++) + sb.Append('\n'); + for (int i = startIdx; i <= endIdx; i++) + sb.Append(value[i]); + + Panel.HighlightText.text = Lexer.SyntaxHighlight(sb.ToString()); + } + + + // TODO indenting + + //private static void DoAutoIndent() + //{ + // int caret = Panel.LastCaretPosition; + // Panel.InputField.Text = Lexer.AutoIndentOnEnter(InputField.text, ref caret); + // InputField.caretPosition = caret; + // + // Panel.InputText.Rebuild(CanvasUpdate.Prelayout); + // InputField.ForceLabelUpdate(); + // InputField.Rebuild(CanvasUpdate.Prelayout); + // + // OnConsoleInputChanged(InputField.text); + //} + #region Evaluating console input public static void ResetConsole(bool logSuccess = true) @@ -149,65 +267,5 @@ The following helper methods are available: #endregion - // Updating and event listeners - - private static readonly KeyCode[] onFocusKeys = - { - KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow, - KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow - }; - - public static void Update() - { - if (EnableCtrlRShortcut) - { - if ((InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl)) - && InputManager.GetKeyDown(KeyCode.R)) - { - var text = Panel.InputField.Text.Trim(); - if (!string.IsNullOrEmpty(text)) - { - Evaluate(text); - return; - } - } - } - - //if (EnableAutoIndent && InputManager.GetKeyDown(KeyCode.Return)) - // DoAutoIndent(); - - //if (EnableAutocompletes && InputField.isFocused) - //{ - // if (InputManager.GetMouseButton(0) || onFocusKeys.Any(it => InputManager.GetKeyDown(it))) - // UpdateAutocompletes(); - //} - } - - // Invoked at most once per frame - private static void OnConsoleInputChanged(string input) - { - // todo update auto completes - - // update syntax highlight - Panel.HighlightText.text = Lexer.SyntaxHighlight(input); - - } - - // TODO? - //private static void DoAutoIndent() - //{ - // int caret = Panel.LastCaretPosition; - // Panel.InputField.Text = Lexer.AutoIndentOnEnter(InputField.text, ref caret); - // InputField.caretPosition = caret; - // - // Panel.InputText.Rebuild(CanvasUpdate.Prelayout); - // InputField.ForceLabelUpdate(); - // InputField.Rebuild(CanvasUpdate.Prelayout); - // - // OnConsoleInputChanged(InputField.text); - //} - - // Autocompletes - } } diff --git a/src/UI/IValues/InteractiveString.cs b/src/UI/IValues/InteractiveString.cs index c970af7..5677c29 100644 --- a/src/UI/IValues/InteractiveString.cs +++ b/src/UI/IValues/InteractiveString.cs @@ -36,7 +36,7 @@ namespace UnityExplorer.UI.IValues { if (s == null) return false; - + return s.Length >= UIManager.MAX_INPUTFIELD_CHARS; } diff --git a/src/UI/Models/InputFieldRef.cs b/src/UI/Models/InputFieldRef.cs index b18c2f5..72df011 100644 --- a/src/UI/Models/InputFieldRef.cs +++ b/src/UI/Models/InputFieldRef.cs @@ -22,7 +22,7 @@ namespace UnityExplorer.UI public InputField InputField; public Text PlaceholderText; - private readonly RectTransform Rect; + public RectTransform Rect; public string Text { @@ -30,6 +30,9 @@ namespace UnityExplorer.UI set => InputField.text = value; } + public bool ReachedMaxVerts => TextGenerator.vertexCount >= UIManager.MAX_TEXT_VERTS; + public TextGenerator TextGenerator => InputField.cachedInputTextGenerator; + private bool updatedWanted; private void OnInputChanged(string value) @@ -41,7 +44,8 @@ namespace UnityExplorer.UI { if (updatedWanted) { - LayoutRebuilder.ForceRebuildLayoutImmediate(Rect); + //LayoutRebuilder.ForceRebuildLayoutImmediate(Rect); + LayoutRebuilder.MarkLayoutForRebuild(Rect); OnValueChanged?.Invoke(InputField.text); updatedWanted = false; diff --git a/src/UI/Panels/CSConsolePanel.cs b/src/UI/Panels/CSConsolePanel.cs index 6988794..c775102 100644 --- a/src/UI/Panels/CSConsolePanel.cs +++ b/src/UI/Panels/CSConsolePanel.cs @@ -18,7 +18,8 @@ namespace UnityExplorer.UI.Panels public override int MinWidth => 400; public override int MinHeight => 300; - public InputFieldRef InputField { get; private set; } + public InputFieldScroller InputScroll { get; private set; } + public InputFieldRef Input => InputScroll.InputField; public Text InputText { get; private set; } public Text HighlightText { get; private set; } @@ -37,19 +38,20 @@ namespace UnityExplorer.UI.Panels public void UseSuggestion(string suggestion) { - string input = InputField.Text; + string input = Input.Text; input = input.Insert(LastCaretPosition, suggestion); - InputField.Text = input; + Input.Text = input; m_desiredCaretFix = LastCaretPosition += suggestion.Length; - var color = InputField.InputField.selectionColor; + var color = Input.InputField.selectionColor; color.a = 0f; - InputField.InputField.selectionColor = color; + Input.InputField.selectionColor = color; } private void InvokeOnValueChanged(string value) { + // Todo show a label instead of just logging if (value.Length == UIManager.MAX_INPUTFIELD_CHARS) ExplorerCore.LogWarning($"Reached maximum InputField character length! ({UIManager.MAX_INPUTFIELD_CHARS})"); @@ -66,23 +68,23 @@ namespace UnityExplorer.UI.Panels { if (!m_fixWaiting) { - EventSystem.current.SetSelectedGameObject(InputField.UIRoot, null); + EventSystem.current.SetSelectedGameObject(InputScroll.UIRoot, null); m_fixWaiting = true; } else { - InputField.InputField.caretPosition = m_desiredCaretFix; - InputField.InputField.selectionFocusPosition = m_desiredCaretFix; - var color = InputField.InputField.selectionColor; + Input.InputField.caretPosition = m_desiredCaretFix; + Input.InputField.selectionFocusPosition = m_desiredCaretFix; + var color = Input.InputField.selectionColor; color.a = m_defaultInputFieldAlpha; - InputField.InputField.selectionColor = color; + Input.InputField.selectionColor = color; m_fixWaiting = false; m_desiredCaretFix = -1; } } - else if (InputField.InputField.caretPosition > 0) - LastCaretPosition = InputField.InputField.caretPosition; + else if (Input.InputField.caretPosition > 0) + LastCaretPosition = Input.InputField.caretPosition; } // Saving @@ -150,14 +152,14 @@ namespace UnityExplorer.UI.Panels int fontSize = 16; var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", CSConsole.STARTUP_TEXT, out var inputScroller, fontSize); - InputField = inputScroller.InputField; - m_defaultInputFieldAlpha = InputField.InputField.selectionColor.a; - InputField.OnValueChanged += InvokeOnValueChanged; + InputScroll = inputScroller; + m_defaultInputFieldAlpha = Input.InputField.selectionColor.a; + Input.OnValueChanged += InvokeOnValueChanged; - var placeHolderText = InputField.PlaceholderText; + var placeHolderText = Input.PlaceholderText; placeHolderText.fontSize = fontSize; - InputText = InputField.InputField.textComponent; + InputText = Input.InputField.textComponent; InputText.supportRichText = false; InputText.color = new Color(1, 1, 1, 0.65f); diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index 09b4138..783c850 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -58,6 +58,7 @@ namespace UnityExplorer.UI internal static readonly Color disabledButtonColor = new Color(0.25f, 0.25f, 0.25f); public const int MAX_INPUTFIELD_CHARS = 16000; + public const int MAX_TEXT_VERTS = 65000; public static UIPanel GetPanel(Panels panel) { diff --git a/src/UI/Widgets/InputFieldScroller.cs b/src/UI/Widgets/InputFieldScroller.cs index 570edbb..747ff61 100644 --- a/src/UI/Widgets/InputFieldScroller.cs +++ b/src/UI/Widgets/InputFieldScroller.cs @@ -25,6 +25,8 @@ namespace UnityExplorer.UI.Utility } } + public Action OnScroll; + internal AutoSliderScrollbar Slider; internal InputFieldRef InputField; @@ -52,25 +54,40 @@ namespace UnityExplorer.UI.Utility internal bool m_wantJumpToBottom; private float m_desiredContentHeight; + private float lastContentPosition; + private float lastViewportHeight; + public override void Update() { + if (this.ContentRect.localPosition.y != lastContentPosition) + { + lastContentPosition = ContentRect.localPosition.y; + OnScroll?.Invoke(); + } + + if (ViewportRect.rect.height != lastViewportHeight) + { + lastViewportHeight = ViewportRect.rect.height; + m_updateWanted = true; + } + if (m_updateWanted) { m_updateWanted = false; ProcessInputText(); - } - float desiredHeight = Math.Max(m_desiredContentHeight, ViewportRect.rect.height); + float desiredHeight = Math.Max(m_desiredContentHeight, ViewportRect.rect.height); - if (ContentRect.rect.height < desiredHeight) - { - ContentRect.sizeDelta = new Vector2(0, desiredHeight); - this.Slider.UpdateSliderHandle(); - } - else if (ContentRect.rect.height > desiredHeight) - { - ContentRect.sizeDelta = new Vector2(0, desiredHeight); - this.Slider.UpdateSliderHandle(); + if (ContentRect.rect.height < desiredHeight) + { + ContentRect.sizeDelta = new Vector2(0, desiredHeight); + this.Slider.UpdateSliderHandle(); + } + else if (ContentRect.rect.height > desiredHeight) + { + ContentRect.sizeDelta = new Vector2(0, desiredHeight); + this.Slider.UpdateSliderHandle(); + } } if (m_wantJumpToBottom) @@ -100,6 +117,9 @@ namespace UnityExplorer.UI.Utility var textGen = InputField.InputField.textComponent.cachedTextGeneratorForLayout; m_desiredContentHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10; + // TODO more intelligent jump. + // We can detect if the caret is outside the viewport area. + // jump to bottom if (InputField.InputField.caretPosition == InputField.Text.Length && InputField.Text.Length > 0