566 lines
20 KiB
C#
566 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using FlaxEditor;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
|
|
namespace Cabrito
|
|
{
|
|
public class ConsoleContentTextBox : Control
|
|
{
|
|
[HideInEditor]
|
|
public ConsoleInputTextBox inputBox;
|
|
|
|
protected TextLayoutOptions _layout;
|
|
|
|
public FontReference Font;
|
|
|
|
public float LineSpacing = 1.0f;
|
|
|
|
public TextWrapping Wrapping;
|
|
|
|
public Color SelectionColor = new Color(0x00, 0x7A, 0xCC, 0xFF);
|
|
public Color BackgroundSelectedColor = Color.Transparent;
|
|
public float BackgroundSelectedFlashSpeed = 0;
|
|
public Color BorderSelectedColor = Color.Transparent;
|
|
public float CaretFlashSpeed = 0;
|
|
public Color BorderColor;
|
|
public Color TextColor = Color.White;
|
|
public int DefaultMargin = 1;
|
|
|
|
private Vector2 scrollOffset = new Vector2(0);
|
|
|
|
private int selectionStartLine = 0;
|
|
private int selectionStartChar = 0;
|
|
private int selectionEndLine = 0;
|
|
private int selectionEndChar = 0;
|
|
|
|
private bool selectionActive;
|
|
public bool HasSelection => !(selectionStartLine == selectionEndLine && selectionStartChar == selectionEndChar);
|
|
|
|
public ConsoleContentTextBox() : base()
|
|
{
|
|
|
|
}
|
|
|
|
public ConsoleContentTextBox(ConsoleInputTextBox inputBox, float x, float y, float width, float height) : base(x, y, width, height)
|
|
{
|
|
this.inputBox = inputBox;
|
|
Height = height;
|
|
|
|
_layout = TextLayoutOptions.Default;
|
|
_layout.VerticalAlignment = TextAlignment.Near;
|
|
_layout.TextWrapping = TextWrapping.WrapChars;
|
|
_layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
|
|
|
|
//IsMultiline = true;
|
|
//IsReadOnly = true;
|
|
//CaretColor = new Color(0f, 0f, 0f, 0f);
|
|
AutoFocus = false;
|
|
}
|
|
|
|
public int GetFontHeight()
|
|
{
|
|
var font = Font.GetFont();
|
|
if (font == null)
|
|
return (int)Height;
|
|
|
|
return (int)Mathf.Round(LineSpacing * font.Height * Scale.Y);
|
|
}
|
|
|
|
struct LineInfo
|
|
{
|
|
public int lineIndex;
|
|
public int lineOffset;
|
|
}
|
|
|
|
private void CalculateVisibleLines(IReadOnlyCollection<string> lines, out int firstVisibleLine, out int lastVisibleLine, out LineInfo[] wrappedLines)
|
|
{
|
|
wrappedLines = null;
|
|
firstVisibleLine = 0;
|
|
lastVisibleLine = 0;
|
|
|
|
var font = Font.GetFont();
|
|
if (!font)
|
|
return;
|
|
|
|
float fontWidth = (int)font.MeasureText("a").X; // hacky, but works for fixed-size fonts...
|
|
int lineMaxChars = (int)(Width / fontWidth);
|
|
int lineMaxLines = (int)(Height / font.Height);
|
|
int numLines = 0;
|
|
int lineIndex = lines.Count - 1;
|
|
List<LineInfo> lineInfos = new List<LineInfo>(lineMaxLines);
|
|
foreach (string line in lines.Reverse())
|
|
{
|
|
int numChars = 0;
|
|
while (numChars < line.Length)
|
|
{
|
|
LineInfo li = new LineInfo();
|
|
li.lineIndex = lineIndex;
|
|
li.lineOffset = numChars;
|
|
lineInfos.Add(li);
|
|
|
|
numChars += lineMaxChars;
|
|
}
|
|
numLines++;
|
|
lineIndex--;
|
|
|
|
if (lineInfos.Count > lineMaxLines)
|
|
break;
|
|
}
|
|
|
|
lineInfos.Reverse();
|
|
wrappedLines = lineInfos.ToArray();
|
|
|
|
//return lines[lines.Count - numLines .. lines.Count]; // C# 8.0...
|
|
firstVisibleLine = lines.Count - numLines;
|
|
lastVisibleLine = lines.Count;
|
|
}
|
|
|
|
public static double accumDrawTime = 0.0;
|
|
public static long accumDrawTimes = 0;
|
|
public override void Draw()
|
|
{
|
|
// Cache data
|
|
var rect = new Rectangle(Vector2.Zero, Size);
|
|
var font = Font.GetFont();
|
|
if (!font)
|
|
return;
|
|
|
|
Profiler.BeginEvent("ConsoleContetTextBoxDraw");
|
|
Stopwatch sw = Stopwatch.StartNew();
|
|
|
|
// 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);
|
|
|
|
var lines = Console.GetLines();
|
|
|
|
if (lines.Count > 0)
|
|
{
|
|
|
|
// Apply view offset and clip mask
|
|
var textClipRectangle = new Rectangle(1, 1, Width - 2, Height - 2);
|
|
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)
|
|
{
|
|
/*Vector2 leftEdge = font.GetCharPosition(text, SelectionLeft + TextPrefix.Length, ref _layout);
|
|
Vector2 rightEdge = font.GetCharPosition(text, SelectionRight + TextPrefix.Length, ref _layout);
|
|
float fontHeight = GetFontHeight();
|
|
float spacing = GetRealLineSpacing();
|
|
|
|
// 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);
|
|
}*/
|
|
}
|
|
|
|
// Make sure lengthy lines are split
|
|
CalculateVisibleLines(lines, out int startLine, out int lastLine, out LineInfo[] wrappedLines);
|
|
|
|
float lineHeight = font.Height;
|
|
float accumHeight = wrappedLines.Length * lineHeight;
|
|
|
|
int selectionLeftLine = selectionStartLine;
|
|
int selectionLeftChar = selectionStartChar;
|
|
int selectionRightLine = selectionEndLine;
|
|
int selectionRightChar = selectionEndChar;
|
|
|
|
if (selectionLeftLine > selectionRightLine || (selectionLeftLine == selectionRightLine && selectionLeftChar > selectionRightChar))
|
|
{
|
|
selectionLeftLine = selectionEndLine;
|
|
selectionLeftChar = selectionEndChar;
|
|
selectionRightLine = selectionStartLine;
|
|
selectionRightChar = selectionStartChar;
|
|
}
|
|
|
|
// render selection
|
|
if (selectionActive)
|
|
{
|
|
|
|
//float alpha = Mathf.Min(1.0f, Mathf.Cos(_animateTime * BackgroundSelectedFlashSpeed) * 0.5f + 1.3f);
|
|
//alpha = alpha * alpha;
|
|
Color selectionColor = SelectionColor;// * alpha;
|
|
|
|
TextLayoutOptions layout = _layout;
|
|
layout.Bounds.Y -= accumHeight - Height;
|
|
for (int i = startLine; i < lastLine; i++)
|
|
{
|
|
string line = lines.ElementAt(i);
|
|
|
|
Rectangle selectionRect = new Rectangle(layout.Bounds.X, layout.Bounds.Y, 0f, 0f);
|
|
|
|
// apply selection
|
|
if (i >= selectionLeftLine && i <= selectionRightLine)
|
|
{
|
|
if (i > selectionLeftLine && i < selectionRightLine)
|
|
{
|
|
// whole line is selected
|
|
Vector2 lineSize = font.MeasureText(line);
|
|
selectionRect.Width = lineSize.X;
|
|
selectionRect.Height = lineSize.Y;
|
|
}
|
|
else if (i == selectionLeftLine)
|
|
{
|
|
if (i < selectionRightLine)
|
|
{
|
|
// right side of the line is selected
|
|
Vector2 leftSize = font.MeasureText(line.Substring(0, selectionLeftChar));
|
|
Vector2 rightSize = font.MeasureText(line.Substring(selectionLeftChar));
|
|
selectionRect.X += leftSize.X;
|
|
selectionRect.Width = rightSize.X;
|
|
selectionRect.Height = rightSize.Y;
|
|
|
|
//int diff = line.Length - selectionLeftChar;
|
|
//line = line.Substring(0, selectionLeftChar) + (diff > 0 ? new string('X', diff) : "");
|
|
}
|
|
else if (i == selectionRightLine)
|
|
{
|
|
// selecting middle of the one line
|
|
Vector2 lineSize = font.MeasureText(line);
|
|
Vector2 leftSize = font.MeasureText(line.Substring(0, selectionLeftChar));
|
|
Vector2 rightSize = font.MeasureText(line.Substring(selectionRightChar));
|
|
|
|
selectionRect.X += leftSize.X;
|
|
selectionRect.Width = lineSize.X - (leftSize.X + rightSize.Y);
|
|
selectionRect.Height = lineSize.Y;
|
|
|
|
//int diff = selectionRightChar - selectionLeftChar;
|
|
//line = line.Substring(0, selectionLeftChar) + (diff > 0 ? new string('X', diff) : "") + line.Substring(selectionRightChar);
|
|
}
|
|
}
|
|
else if (i == selectionRightLine)
|
|
{
|
|
// left side of the line is selected
|
|
Vector2 leftSize = font.MeasureText(line.Substring(0, selectionRightChar));
|
|
selectionRect.Width = leftSize.X;
|
|
selectionRect.Height = leftSize.Y;
|
|
|
|
//line = (selectionRightChar > 0 ? new string('X', selectionRightChar) : "") + line.Substring(selectionRightChar);
|
|
}
|
|
}
|
|
|
|
|
|
Render2D.FillRectangle(selectionRect, selectionColor);
|
|
|
|
layout.Bounds.Y += lineHeight;
|
|
}
|
|
}
|
|
|
|
// render lines
|
|
{
|
|
TextLayoutOptions layout = _layout;
|
|
layout.Bounds.Y -= accumHeight - Height;
|
|
for (int i = startLine; i < lastLine; i++)
|
|
{
|
|
string line = lines.ElementAt(i);
|
|
Render2D.DrawText(font, line, TextColor, ref layout);
|
|
layout.Bounds.Y += lineHeight;
|
|
}
|
|
}
|
|
|
|
/*if (CaretPosition > -1)
|
|
{
|
|
var prefixSize = TextPrefix != "" ? font.MeasureText(TextPrefix) : new Vector2();
|
|
var 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();
|
|
}
|
|
sw.Stop();
|
|
accumDrawTime += sw.Elapsed.TotalSeconds;
|
|
accumDrawTimes++;
|
|
Profiler.EndEvent();
|
|
}
|
|
|
|
|
|
private void OnSelectingBegin()
|
|
{
|
|
if (selectionActive)
|
|
return;
|
|
|
|
selectionActive = true;
|
|
StartMouseCapture();
|
|
}
|
|
|
|
private void OnSelectingEnd()
|
|
{
|
|
if (!selectionActive)
|
|
return;
|
|
|
|
selectionActive = false;
|
|
EndMouseCapture();
|
|
}
|
|
|
|
public bool HitTestText(Vector2 location, out int hitLine, out int hitChar)
|
|
{
|
|
hitLine = 0;
|
|
hitChar = 0;
|
|
var font = Font.GetFont();
|
|
if (font == null)
|
|
return false;
|
|
|
|
var lines = Console.GetLines();
|
|
CalculateVisibleLines(lines, out int startLine, out int lastLine, out LineInfo[] wrappedLines);
|
|
|
|
TextLayoutOptions layout = _layout;
|
|
float lineHeight = font.Height;
|
|
float visibleHeight = wrappedLines.Length * lineHeight;
|
|
float top = layout.Bounds.Bottom - visibleHeight;
|
|
int lineMaxLines = (int)(Height / font.Height);
|
|
|
|
int hiddenLines = 0;
|
|
if (wrappedLines.Length > lineMaxLines)
|
|
hiddenLines = wrappedLines.Length - lineMaxLines;
|
|
//if (top < layout.Bounds.Top)
|
|
// hiddenLines = (int)Math.Ceiling((layout.Bounds.Top - top) / (float)lineHeight);
|
|
|
|
int hitWrappedLine = (int)((location.Y - top) / lineHeight) + hiddenLines;
|
|
if (hitWrappedLine < 0 || hitWrappedLine >= wrappedLines.Length)
|
|
return false;
|
|
|
|
hitLine = wrappedLines[hitWrappedLine].lineIndex;
|
|
string line = lines.ElementAt(hitLine);
|
|
|
|
layout.Bounds.Y = top + ((hitWrappedLine) * lineHeight) + 22.5f;
|
|
layout.Bounds.Height = top + visibleHeight;
|
|
/*if (layout.Bounds.Y < 0)
|
|
{
|
|
layout.Bounds.Y = 1;
|
|
}*/
|
|
|
|
hitChar = font.HitTestText(line, location, ref layout);
|
|
return true;
|
|
}
|
|
|
|
/*public int CharIndexAtPoint(ref Vector2 location)
|
|
{
|
|
return HitTestText(location + _viewOffset);
|
|
}*/
|
|
|
|
|
|
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
|
{
|
|
bool ret = false;
|
|
if (button == MouseButton.Left && !IsFocused)
|
|
{
|
|
Focus();
|
|
ret = true;
|
|
}
|
|
|
|
var lines = Console.GetLines();
|
|
if (button == MouseButton.Left && lines.Count > 0)
|
|
{
|
|
bool selectionStarted = !selectionActive;
|
|
Focus();
|
|
OnSelectingBegin();
|
|
|
|
//FlaxEngine.Debug.Log("mousedown, started: " + selectionStarted.ToString());
|
|
|
|
if (HitTestText(location, out int hitLine, out int hitChar))
|
|
{
|
|
selectionStartLine = hitLine;
|
|
selectionStartChar = hitChar;
|
|
selectionEndLine = hitLine;
|
|
selectionEndChar = hitChar;
|
|
}
|
|
else
|
|
throw new Exception("no???");
|
|
|
|
|
|
// Select range with shift
|
|
/*if (_selectionStart != -1 && RootWindow.GetKey(KeyboardKeys.Shift) && SelectionLength == 0)
|
|
{
|
|
if (hitPos < _selectionStart)
|
|
SetSelection(hitPos, _selectionStart);
|
|
else
|
|
SetSelection(_selectionStart, hitPos);
|
|
}
|
|
else
|
|
{
|
|
SetSelection(hitPos);
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public override void OnMouseMove(Vector2 location)
|
|
{
|
|
if (selectionActive)
|
|
{
|
|
if (HitTestText(location, out int hitLine, out int hitChar))
|
|
{
|
|
selectionEndLine = hitLine;
|
|
selectionEndChar = hitChar;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
|
{
|
|
if (button == MouseButton.Left)
|
|
{
|
|
OnSelectingEnd();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public class ConsoleContentTextBox_Old : ConsoleTextBoxBase
|
|
{
|
|
[HideInEditor]
|
|
public ConsoleInputTextBox inputBox;
|
|
|
|
private string _textCache;
|
|
private long _textCacheLines;
|
|
public override string Text
|
|
{
|
|
get
|
|
{
|
|
var lines = Console.GetLines();
|
|
if (_textCache == null || _textCacheLines != lines.Count)
|
|
{
|
|
//Deselect();
|
|
//ResetViewOffset();
|
|
|
|
_textCache = string.Join("\n", lines);
|
|
_textCacheLines = lines.Count;
|
|
|
|
OnTextChanged();
|
|
}
|
|
return _textCache;
|
|
}
|
|
set => _textCache = value;
|
|
}
|
|
|
|
public ConsoleContentTextBox_Old(ConsoleInputTextBox inputBox, float x, float y, float width, float height) : base(x, y, width, height)
|
|
{
|
|
this.inputBox = inputBox;
|
|
Height = height;
|
|
|
|
IsMultiline = true;
|
|
IsReadOnly = true;
|
|
CaretColor = new Color(0f, 0f, 0f, 0f);
|
|
AutoFocus = false;
|
|
}
|
|
|
|
public override void OnGotFocus()
|
|
{
|
|
base.OnGotFocus();
|
|
}
|
|
|
|
public override void OnLostFocus()
|
|
{
|
|
ClearSelection();
|
|
base.OnLostFocus();
|
|
}
|
|
|
|
protected override void OnTextChanged()
|
|
{
|
|
base.OnTextChanged();
|
|
ScrollToEnd();
|
|
}
|
|
|
|
public override bool OnKeyDown(KeyboardKeys key)
|
|
{
|
|
bool ret;
|
|
switch (key)
|
|
{
|
|
case KeyboardKeys.Escape:
|
|
ret = true; // disable text restoration
|
|
break;
|
|
case KeyboardKeys.Home:
|
|
case KeyboardKeys.End:
|
|
// TODO: scroll top and scroll bottom
|
|
ret = true;
|
|
break;
|
|
case KeyboardKeys.ArrowUp:
|
|
case KeyboardKeys.ArrowDown:
|
|
case KeyboardKeys.ArrowLeft:
|
|
case KeyboardKeys.ArrowRight:
|
|
ret = true; // input box has priority
|
|
break;
|
|
case KeyboardKeys.PageUp:
|
|
case KeyboardKeys.PageDown:
|
|
return true;
|
|
default:
|
|
ret = base.OnKeyDown(key);
|
|
break;
|
|
}
|
|
|
|
if (inputBox == null)
|
|
return ret;
|
|
return inputBox.OnKeyDown(key);
|
|
}
|
|
|
|
public override void OnKeyUp(KeyboardKeys key)
|
|
{
|
|
base.OnKeyUp(key);
|
|
if (inputBox != null)
|
|
inputBox.OnKeyUp(key);
|
|
}
|
|
|
|
public override bool OnCharInput(char c)
|
|
{
|
|
if (inputBox == null)
|
|
return base.OnCharInput(c);
|
|
|
|
Focus(inputBox);
|
|
return inputBox.OnCharInput(c);
|
|
}
|
|
}
|
|
}
|