using System.Diagnostics; using FlaxEngine; using FlaxEngine.GUI; namespace Game; // Mostly based on TextBox public class ConsoleTextBoxBase : TextBoxBase { private readonly Stopwatch lastDoubleClick = new Stopwatch(); protected TextLayoutOptions _layout; private bool doubleClicked; private Float2 lastDoubleClickLocation = new Float2(0, 0); /// /// Gets or sets the color of the selection (Transparent if not used). /// [EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The color of the selection (Transparent if not used).")] public Color SelectionColor = new Color(0x00, 0x7A, 0xCC); /// /// Gets or sets the color of the text. /// [EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The color of the text.")] public Color TextColor = Color.White; //[HideInEditor] //public override string Text => _text; public ConsoleTextBoxBase() { } public ConsoleTextBoxBase(float x, float y, float width, float height) : base(false, x, y, width) { Height = height; IsReadOnly = false; CaretColor = new Color(1f, 1f, 1f, 1f); AutoFocus = true; EndEditOnClick = false; _layout = TextLayoutOptions.Default; _layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center; _layout.TextWrapping = TextWrapping.NoWrap; _layout.Bounds = new Rectangle(0, 0, Width, Height); //new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2); } /// /// Gets or sets the text wrapping within the control bounds. /// [EditorDisplay("Style")] [EditorOrder(2000)] [Tooltip("The text wrapping within the control bounds.")] public TextWrapping Wrapping { get => _layout.TextWrapping; set => _layout.TextWrapping = value; } /// /// Gets or sets the font. /// [EditorDisplay("Style")] [EditorOrder(2000)] public FontReference Font { get; set; } [HideInEditor] public virtual string TextPrefix { get; set; } = ""; /*protected override void SetText(string value) { // Prevent from null problems if (value == null) value = string.Empty; // Filter text if (value.IndexOf('\r') != -1) value = value.Replace("\r", ""); // Clamp length if (value.Length > MaxLength) value = value.Substring(0, MaxLength); // Ensure to use only single line if (_isMultiline == false && value.Length > 0) { // Extract only the first line value = value.GetLines()[0]; } if (Text != value) { Deselect(); ResetViewOffset(); Text = value; OnTextChanged(); } }*/ public int GetFontHeight() { Font font = Font.GetFont(); if (font == null) return (int)Height; return (int)Mathf.Round(font.Height * Scale.Y); } public override void Clear() { // Can't clear the text while user is editing it... bool oldEditing = _isEditing; _isEditing = false; base.Clear(); _isEditing = oldEditing; } public override void ResetViewOffset() { TargetViewOffset = new Float2(0, 0); } /*public void ScrollToEnd() { float maxY = TextSize.Y - Height; float spacing = GetRealLineSpacing(); maxY += spacing; TargetViewOffset = new Float2(0, Math.Max(0, maxY)); }*/ public override void ScrollToCaret() { if (Text.Length == 0) return; Rectangle caretBounds = CaretBounds; float maxY = TextSize.Y - Height; Float2 newLocation = CaretBounds.Location; TargetViewOffset = Float2.Clamp(newLocation, new Float2(0, 0), new Float2(_targetViewOffset.X, maxY)); } /*const bool smoothScrolling = false; public override bool OnMouseWheel(Float2 location, float delta) { if (!IsMultiline || Text.Length == 0) return false; if (!smoothScrolling) delta = GetFontHeight() * Math.Sign(delta) * 3; else delta *= 30; float maxY = TextSize.Y - Height; float offset = GetRealLineSpacing(); maxY += offset; TargetViewOffset = Float2.Clamp(_targetViewOffset - new Float2(0, delta), new Float2(0, offset), new Float2(_targetViewOffset.X, maxY)); return true; }*/ public override Float2 GetTextSize() { Font font = Font.GetFont(); if (font == null) return Float2.Zero; return font.MeasureText(Text, ref _layout); } public override Float2 GetCharPosition(int index, out float height) { Font font = Font.GetFont(); if (font == null) { height = Height; return Float2.Zero; } height = GetFontHeight(); return font.GetCharPosition(Text, index, ref _layout); } public override int HitTestText(Float2 location) { Font font = Font.GetFont(); if (font == null) return 0; if (TextPrefix != "") { Float2 prefixSize = font.MeasureText(TextPrefix); location.X -= prefixSize.X; } return font.HitTestText(Text, location, ref _layout); } protected override void OnIsMultilineChanged() { base.OnIsMultilineChanged(); _layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center; } public override bool OnKeyDown(KeyboardKeys key) { bool shiftDown = Root.GetKey(KeyboardKeys.Shift); bool ctrlDown = Root.GetKey(KeyboardKeys.Control); if (shiftDown && key == KeyboardKeys.Delete) Cut(); else if (ctrlDown && key == KeyboardKeys.Insert) Copy(); else if (shiftDown && key == KeyboardKeys.Insert) Paste(); if (shiftDown && key == KeyboardKeys.Home) { if (!IsReadOnly) SetSelection(_selectionStart, 0); return true; } if (shiftDown && key == KeyboardKeys.End) { if (!IsReadOnly) SetSelection(_selectionStart, TextLength); return true; } return base.OnKeyDown(key); } public override bool OnMouseDown(Float2 location, MouseButton button) { if (doubleClicked && lastDoubleClick.Elapsed.TotalSeconds < 0.5 && location == lastDoubleClickLocation) // Windows defaults to 500ms window { doubleClicked = false; if (OnMouseTripleClick(location, button)) return true; } return base.OnMouseDown(location, button); } public override bool OnMouseDoubleClick(Float2 location, MouseButton button) { doubleClicked = true; lastDoubleClick.Restart(); lastDoubleClickLocation = location; return base.OnMouseDoubleClick(location, button); } public bool OnMouseTripleClick(Float2 location, MouseButton button) { if (!IsMultiline) SelectAll(); else // TODO: select the line SelectAll(); return true; } protected override void OnSizeChanged() { base.OnSizeChanged(); _layout.Bounds = TextRectangle; } public override void Draw() { // Cache data Rectangle rect = new Rectangle(Float2.Zero, Size); Font font = Font.GetFont(); if (!font) return; // Background Color backColor = BackgroundColor; if (IsMouseOver) backColor = BackgroundSelectedColor; if (backColor.A > 0.0f) Render2D.FillRectangle(rect, backColor); Color borderColor = IsFocused ? BorderSelectedColor : BorderColor; if (borderColor.A > 0.0f) Render2D.DrawRectangle(rect, borderColor); //string text = TextPrefix + Text; string text = TextPrefix + Text; if (text.Length == 0) return; // Apply view offset and clip mask Render2D.PushClip(TextClipRectangle); bool useViewOffset = !_viewOffset.IsZero; if (useViewOffset) Render2D.PushTransform(Matrix3x3.Translation2D(-_viewOffset)); // Check if any text is selected to draw selection if (HasSelection) { Float2 leftEdge = font.GetCharPosition(text, SelectionLeft + TextPrefix.Length, ref _layout); Float2 rightEdge = font.GetCharPosition(text, SelectionRight + TextPrefix.Length, ref _layout); float fontHeight = GetFontHeight(); // Draw selection background float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f); alpha = alpha * alpha; Color selectionColor = SelectionColor * alpha; int selectedLinesCount = 1 + Mathf.FloorToInt((rightEdge.Y - leftEdge.Y) / fontHeight); if (selectedLinesCount == 1) // Selected is part of single line { Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, rightEdge.X - leftEdge.X, fontHeight); Render2D.FillRectangle(r1, selectionColor); } else // Selected is more than one line { float leftMargin = _layout.Bounds.Location.X; Rectangle r1 = new Rectangle(leftEdge.X, leftEdge.Y, 1000000000, fontHeight); Render2D.FillRectangle(r1, selectionColor); for (int i = 3; i <= selectedLinesCount; i++) { leftEdge.Y += fontHeight; Rectangle r = new Rectangle(leftMargin, leftEdge.Y, 1000000000, fontHeight); Render2D.FillRectangle(r, selectionColor); } Rectangle r2 = new Rectangle(leftMargin, rightEdge.Y, rightEdge.X - leftMargin, fontHeight); Render2D.FillRectangle(r2, selectionColor); } } Render2D.DrawText(font, text, TextColor, ref _layout); if (CaretPosition > -1) { Float2 prefixSize = TextPrefix != "" ? font.MeasureText(TextPrefix) : new Float2(); Rectangle caretBounds = CaretBounds; caretBounds.X += prefixSize.X; float alpha = Mathf.Saturate(Mathf.Cos(_animateTime * CaretFlashSpeed) * 0.5f + 0.7f); alpha = alpha * alpha * alpha * alpha * alpha * alpha; Render2D.FillRectangle(caretBounds, CaretColor * alpha); } // Restore rendering state if (useViewOffset) Render2D.PopTransform(); Render2D.PopClip(); } public override void Paste() { if (IsReadOnly) return; string clipboardText = Clipboard.Text; // Handle newlines in clipboard text if (!string.IsNullOrEmpty(clipboardText)) { // TODO: probably better to just split these lines and parse each line separately clipboardText = clipboardText.Replace("\r\n", ""); clipboardText = clipboardText.Replace("\n", ""); } if (!string.IsNullOrEmpty(clipboardText)) { int right = SelectionRight; Insert(clipboardText); SetSelection(Mathf.Max(right, 0) + clipboardText.Length); } } }