Initial commit
This commit is contained in:
421
Source/Game/Cabrito/Console/ConsoleTextBoxBase.cs
Normal file
421
Source/Game/Cabrito/Console/ConsoleTextBoxBase.cs
Normal file
@@ -0,0 +1,421 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user