422 lines
14 KiB
C#
422 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using FlaxEditor;
|
|
using FlaxEngine;
|
|
using FlaxEngine.GUI;
|
|
|
|
namespace Cabrito
|
|
{
|
|
// Mostly based on TextBox
|
|
public class ConsoleTextBoxBase : TextBoxBase
|
|
{
|
|
protected TextLayoutOptions _layout;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the text wrapping within the control bounds.
|
|
/// </summary>
|
|
[EditorDisplay("Style"), EditorOrder(2000), Tooltip("The text wrapping within the control bounds.")]
|
|
public TextWrapping Wrapping
|
|
{
|
|
get => _layout.TextWrapping;
|
|
set => _layout.TextWrapping = value;
|
|
}
|
|
|
|
[EditorDisplay("Style"), EditorOrder(2000), Tooltip("The line spacing of the text.")]
|
|
public float LineSpacing
|
|
{
|
|
get => _layout.BaseLinesGapScale;
|
|
set
|
|
{
|
|
// Round to nearest pixel in order to avoid uneven line heights
|
|
float newValue = value;
|
|
var font = Font.GetFont();
|
|
if (font != null)
|
|
{
|
|
float actualHeight = font.Height * Scale.Y;
|
|
newValue = Mathf.Round(newValue * actualHeight) / actualHeight;
|
|
}
|
|
|
|
_layout.BaseLinesGapScale = newValue;
|
|
OnTextChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the font.
|
|
/// </summary>
|
|
[EditorDisplay("Style"), EditorOrder(2000)]
|
|
public FontReference Font { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the color of the text.
|
|
/// </summary>
|
|
[EditorDisplay("Style"), EditorOrder(2000), Tooltip("The color of the text.")]
|
|
public Color TextColor { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the color of the selection (Transparent if not used).
|
|
/// </summary>
|
|
[EditorDisplay("Style"), EditorOrder(2000), Tooltip("The color of the selection (Transparent if not used).")]
|
|
public Color SelectionColor { get; set; }
|
|
|
|
[HideInEditor]
|
|
public virtual string TextPrefix { get; set; } = "";
|
|
|
|
//[HideInEditor]
|
|
//public override string Text => _text;
|
|
|
|
public ConsoleTextBoxBase() : base()
|
|
{
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
_layout = TextLayoutOptions.Default;
|
|
_layout.VerticalAlignment = IsMultiline ? TextAlignment.Near : TextAlignment.Center;
|
|
_layout.TextWrapping = TextWrapping.NoWrap;
|
|
_layout.Bounds = new Rectangle(DefaultMargin, 1, Width - 2 * DefaultMargin, Height - 2);
|
|
|
|
var style = Style.Current;
|
|
Font = new FontReference(style.FontMedium);
|
|
TextColor = style.Foreground;
|
|
SelectionColor = style.BackgroundSelected;
|
|
}
|
|
|
|
/*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()
|
|
{
|
|
var font = Font.GetFont();
|
|
if (font == null)
|
|
return (int)Height;
|
|
|
|
return (int)Mathf.Round(LineSpacing * font.Height * Scale.Y);
|
|
}
|
|
|
|
private float GetRealLineSpacing()
|
|
{
|
|
return GetFontHeight() * (1.0f - LineSpacing);
|
|
}
|
|
|
|
public override void Clear()
|
|
{
|
|
// Can't clear the text while user is editing it...
|
|
var oldEditing = _isEditing;
|
|
_isEditing = false;
|
|
base.Clear();
|
|
_isEditing = oldEditing;
|
|
}
|
|
|
|
public override void ResetViewOffset()
|
|
{
|
|
TargetViewOffset = new Vector2(0, GetRealLineSpacing());
|
|
}
|
|
|
|
public void ScrollToEnd()
|
|
{
|
|
float maxY = TextSize.Y - Height;
|
|
float spacing = GetRealLineSpacing();
|
|
maxY += spacing;
|
|
|
|
TargetViewOffset = new Vector2(0, Math.Max(0, maxY));
|
|
}
|
|
|
|
public override void ScrollToCaret()
|
|
{
|
|
if (Text.Length == 0)
|
|
return;
|
|
|
|
Rectangle caretBounds = CaretBounds;
|
|
|
|
float maxY = TextSize.Y - Height;
|
|
float spacing = GetRealLineSpacing();
|
|
maxY += spacing;
|
|
|
|
Vector2 newLocation = CaretBounds.Location;
|
|
newLocation.Y += spacing;
|
|
TargetViewOffset = Vector2.Clamp(newLocation, new Vector2(0, spacing), new Vector2(_targetViewOffset.X, maxY));
|
|
}
|
|
|
|
const bool smoothScrolling = false;
|
|
|
|
public override bool OnMouseWheel(Vector2 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 = Vector2.Clamp(_targetViewOffset - new Vector2(0, delta), new Vector2(0, offset), new Vector2(_targetViewOffset.X, maxY));
|
|
return true;
|
|
}
|
|
|
|
public override Vector2 GetTextSize()
|
|
{
|
|
var font = Font.GetFont();
|
|
if (font == null)
|
|
return Vector2.Zero;
|
|
|
|
return font.MeasureText(Text, ref _layout);
|
|
}
|
|
|
|
public override Vector2 GetCharPosition(int index, out float height)
|
|
{
|
|
var font = Font.GetFont();
|
|
if (font == null)
|
|
{
|
|
height = Height;
|
|
return Vector2.Zero;
|
|
}
|
|
|
|
height = GetFontHeight();
|
|
return font.GetCharPosition(Text, index, ref _layout);
|
|
}
|
|
|
|
public override int HitTestText(Vector2 location)
|
|
{
|
|
var font = Font.GetFont();
|
|
if (font == null)
|
|
return 0;
|
|
|
|
if (TextPrefix != "")
|
|
{
|
|
var 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;
|
|
}
|
|
else if (shiftDown && key == KeyboardKeys.End)
|
|
{
|
|
if (!IsReadOnly)
|
|
SetSelection(_selectionStart, TextLength);
|
|
return true;
|
|
}
|
|
return base.OnKeyDown(key);
|
|
}
|
|
|
|
bool doubleClicked = false;
|
|
System.Diagnostics.Stopwatch lastDoubleClick = new System.Diagnostics.Stopwatch();
|
|
Vector2 lastDoubleClickLocation = new Vector2(0, 0);
|
|
|
|
public override bool OnMouseDown(Vector2 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(Vector2 location, MouseButton button)
|
|
{
|
|
doubleClicked = true;
|
|
lastDoubleClick.Restart();
|
|
lastDoubleClickLocation = location;
|
|
|
|
return base.OnMouseDoubleClick(location, button);
|
|
}
|
|
|
|
public bool OnMouseTripleClick(Vector2 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
|
|
var rect = new Rectangle(Vector2.Zero, Size);
|
|
var 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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
Render2D.DrawText(font, text, TextColor, ref _layout);
|
|
|
|
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();
|
|
}
|
|
|
|
public override void Paste()
|
|
{
|
|
if (IsReadOnly)
|
|
return;
|
|
|
|
var 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))
|
|
{
|
|
var right = SelectionRight;
|
|
Insert(clipboardText);
|
|
SetSelection(Mathf.Max(right, 0) + clipboardText.Length);
|
|
}
|
|
}
|
|
}
|
|
}
|