mirror of
https://github.com/AlexMacocian/MonoGame.Extended.git
synced 2026-07-22 18:29:30 +00:00
328 lines
9.4 KiB
C#
328 lines
9.4 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Input;
|
|
using MonoGame.Extended.Input.InputListeners;
|
|
|
|
namespace MonoGame.Extended.Gui.Controls
|
|
{
|
|
public class TextBox2 : Control
|
|
{
|
|
public TextBox2()
|
|
: this(null)
|
|
{
|
|
}
|
|
|
|
public TextBox2(string text)
|
|
{
|
|
Text = text ?? string.Empty;
|
|
HorizontalTextAlignment = HorizontalAlignment.Left;
|
|
VerticalTextAlignment = VerticalAlignment.Top;
|
|
}
|
|
|
|
|
|
private const float _caretBlinkRate = 0.53f;
|
|
private float _nextCaretBlink = _caretBlinkRate;
|
|
private bool _isCaretVisible = true;
|
|
|
|
private readonly List<StringBuilder> _lines = new List<StringBuilder>();
|
|
public string Text
|
|
{
|
|
get => string.Concat(_lines.SelectMany(s => $"{s}\n"));
|
|
set
|
|
{
|
|
_lines.Clear();
|
|
|
|
var line = new StringBuilder();
|
|
|
|
for (var i = 0; i < value.Length; i++)
|
|
{
|
|
var c = value[i];
|
|
|
|
if (c == '\n')
|
|
{
|
|
_lines.Add(line);
|
|
line = new StringBuilder();
|
|
}
|
|
else if(c != '\r')
|
|
{
|
|
line.Append(c);
|
|
}
|
|
}
|
|
|
|
_lines.Add(line);
|
|
}
|
|
}
|
|
|
|
public int CaretIndex => ColumnIndex * LineIndex + ColumnIndex;
|
|
public int LineIndex { get; set; }
|
|
public int ColumnIndex { get; set; }
|
|
public int TabStops { get; set; } = 4;
|
|
|
|
public override IEnumerable<Control> Children => Enumerable.Empty<Control>();
|
|
|
|
public string GetLineText(int lineIndex) => _lines[lineIndex].ToString();
|
|
public int GetLineLength(int lineIndex) => _lines[lineIndex].Length;
|
|
|
|
public override Size GetContentSize(IGuiContext context)
|
|
{
|
|
var font = Font ?? context.DefaultFont;
|
|
var stringSize = (Size)font.MeasureString(Text ?? string.Empty);
|
|
return new Size(stringSize.Width, stringSize.Height < font.LineHeight ? font.LineHeight : stringSize.Height);
|
|
}
|
|
|
|
public override bool OnKeyPressed(IGuiContext context, KeyboardEventArgs args)
|
|
{
|
|
switch (args.Key)
|
|
{
|
|
case Keys.Tab:
|
|
Tab();
|
|
break;
|
|
case Keys.Back:
|
|
Backspace();
|
|
break;
|
|
case Keys.Delete:
|
|
Delete();
|
|
break;
|
|
case Keys.Left:
|
|
Left();
|
|
break;
|
|
case Keys.Right:
|
|
Right();
|
|
break;
|
|
case Keys.Up:
|
|
Up();
|
|
break;
|
|
case Keys.Down:
|
|
Down();
|
|
break;
|
|
case Keys.Home:
|
|
Home();
|
|
break;
|
|
case Keys.End:
|
|
End();
|
|
break;
|
|
case Keys.Enter:
|
|
Type('\n');
|
|
return true;
|
|
default:
|
|
if (args.Character.HasValue)
|
|
Type(args.Character.Value);
|
|
|
|
break;
|
|
}
|
|
|
|
_isCaretVisible = true;
|
|
return base.OnKeyPressed(context, args);
|
|
}
|
|
|
|
public void Type(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '\n':
|
|
var lineText = GetLineText(LineIndex);
|
|
var left = lineText.Substring(0, ColumnIndex);
|
|
var right = lineText.Substring(ColumnIndex);
|
|
_lines.Insert(LineIndex + 1, new StringBuilder(right));
|
|
_lines[LineIndex] = new StringBuilder(left);
|
|
LineIndex++;
|
|
Home();
|
|
break;
|
|
case '\t':
|
|
Tab();
|
|
break;
|
|
default:
|
|
_lines[LineIndex].Insert(ColumnIndex, c);
|
|
ColumnIndex++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void Backspace()
|
|
{
|
|
if (ColumnIndex == 0 && LineIndex > 0)
|
|
{
|
|
var topLineLength = GetLineLength(LineIndex - 1);
|
|
|
|
if (RemoveLineBreak(LineIndex - 1))
|
|
{
|
|
LineIndex--;
|
|
ColumnIndex = topLineLength;
|
|
}
|
|
}
|
|
else if (Left())
|
|
{
|
|
RemoveCharacter(LineIndex, ColumnIndex);
|
|
}
|
|
}
|
|
|
|
public void Delete()
|
|
{
|
|
var lineLength = GetLineLength(LineIndex);
|
|
|
|
if (ColumnIndex == lineLength)
|
|
RemoveLineBreak(LineIndex);
|
|
else
|
|
RemoveCharacter(LineIndex, ColumnIndex);
|
|
}
|
|
|
|
public void RemoveCharacter(int lineIndex, int columnIndex)
|
|
{
|
|
_lines[lineIndex].Remove(columnIndex, 1);
|
|
}
|
|
|
|
public bool RemoveLineBreak(int lineIndex)
|
|
{
|
|
if (lineIndex < _lines.Count - 1)
|
|
{
|
|
var topLine = _lines[lineIndex];
|
|
var bottomLine = _lines[lineIndex + 1];
|
|
_lines.RemoveAt(lineIndex + 1);
|
|
topLine.Append(bottomLine);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool Home()
|
|
{
|
|
ColumnIndex = 0;
|
|
return true;
|
|
}
|
|
|
|
public bool End()
|
|
{
|
|
ColumnIndex = GetLineLength(LineIndex);
|
|
return true;
|
|
}
|
|
|
|
public bool Up()
|
|
{
|
|
if (LineIndex > 0)
|
|
{
|
|
LineIndex--;
|
|
|
|
if (ColumnIndex > GetLineLength(LineIndex))
|
|
ColumnIndex = GetLineLength(LineIndex);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool Down()
|
|
{
|
|
if (LineIndex < _lines.Count - 1)
|
|
{
|
|
LineIndex++;
|
|
|
|
if (ColumnIndex > GetLineLength(LineIndex))
|
|
ColumnIndex = GetLineLength(LineIndex);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool Left()
|
|
{
|
|
if (ColumnIndex == 0)
|
|
{
|
|
if (LineIndex == 0)
|
|
return false;
|
|
|
|
LineIndex--;
|
|
ColumnIndex = GetLineLength(LineIndex);
|
|
}
|
|
else
|
|
{
|
|
ColumnIndex--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool Right()
|
|
{
|
|
if (ColumnIndex == _lines[LineIndex].Length)
|
|
{
|
|
if (LineIndex == _lines.Count - 1)
|
|
return false;
|
|
|
|
LineIndex++;
|
|
ColumnIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
ColumnIndex++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool Tab()
|
|
{
|
|
var spaces = TabStops - ColumnIndex % TabStops;
|
|
|
|
for (var s = 0; s < spaces; s++)
|
|
Type(' ');
|
|
|
|
return spaces > 0;
|
|
}
|
|
|
|
public override void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
|
|
{
|
|
base.Draw(context, renderer, deltaSeconds);
|
|
|
|
var text = Text;
|
|
var textInfo = GetTextInfo(context, text, ContentRectangle, HorizontalTextAlignment, VerticalTextAlignment);
|
|
|
|
if (!string.IsNullOrWhiteSpace(textInfo.Text))
|
|
renderer.DrawText(textInfo.Font, textInfo.Text, textInfo.Position + TextOffset, textInfo.Color, textInfo.ClippingRectangle);
|
|
|
|
if (IsFocused)
|
|
{
|
|
var caretRectangle = GetCaretRectangle(textInfo);
|
|
|
|
if (_isCaretVisible)
|
|
renderer.DrawRectangle(caretRectangle, TextColor);
|
|
|
|
_nextCaretBlink -= deltaSeconds;
|
|
|
|
if (_nextCaretBlink <= 0)
|
|
{
|
|
_isCaretVisible = !_isCaretVisible;
|
|
_nextCaretBlink = _caretBlinkRate;
|
|
}
|
|
}
|
|
}
|
|
|
|
private Rectangle GetCaretRectangle(TextInfo textInfo)
|
|
{
|
|
var font = textInfo.Font;
|
|
var text = GetLineText(LineIndex);
|
|
var offset = new Vector2(0, font.LineHeight * LineIndex);
|
|
var caretRectangle = font.GetStringRectangle(text.Substring(0, ColumnIndex), textInfo.Position + offset);
|
|
|
|
// TODO: Finish the caret position stuff when it's outside the clipping rectangle
|
|
if (caretRectangle.Right > ClippingRectangle.Right)
|
|
textInfo.Position.X -= caretRectangle.Right - ClippingRectangle.Right;
|
|
|
|
caretRectangle.X = caretRectangle.Right < ClippingRectangle.Right ? caretRectangle.Right : ClippingRectangle.Right;
|
|
caretRectangle.Width = 1;
|
|
|
|
if (caretRectangle.Left < ClippingRectangle.Left)
|
|
{
|
|
textInfo.Position.X += ClippingRectangle.Left - caretRectangle.Left;
|
|
caretRectangle.X = ClippingRectangle.Left;
|
|
}
|
|
|
|
return (Rectangle)caretRectangle;
|
|
}
|
|
}
|
|
} |