Files
MonoGame.Extended/Source/MonoGame.Extended.Gui/Controls/GuiTextBox.cs
T
tspayne87 97d237f11f [GUI] Propegation changed also adding in fix for Issue #428 (#441)
* Adding in changes to deal with custom control types for loading from a GuiScreen, also adding in block text if a width for the element exists.  Adding in Padding to some elements to give the developer some more options in styles.  Adding in some changes to the textbox to deal with selecting a section of text.

* Adding in a few changes to deal with propagation down the GuiControl tree, this was added to most events.  Also, adding in the idea of a form and submit button to deal with keyboard controls when inside of a form.  This gives simalar behavier to how forms work in the browser.

* Adding in the enter key skip for the textbox.

* Fixing issue where GuiSystem would call initialize twice

* Adding in a fix for Issue #428, by leveraging the propegation system to deal with hover styles.
2017-11-26 20:48:57 +10:00

292 lines
11 KiB
C#

using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input.InputListeners;
using System.Collections.Generic;
using MonoGame.Extended.BitmapFonts;
namespace MonoGame.Extended.Gui.Controls
{
public class GuiTextBox : GuiControl
{
public GuiTextBox()
: this(null)
{
}
public GuiTextBox(GuiSkin skin, string text = null)
: base(skin)
{
Text = text ?? string.Empty;
}
public int SelectionStart { get; set; }
public char? PasswordCharacter { get; set; }
public Thickness Padding { get; set; }
protected override void OnTextChanged()
{
if (SelectionStart > Text.Length)
SelectionStart = Text.Length;
base.OnTextChanged();
}
protected override Size2 CalculateDesiredSize(IGuiContext context, Size2 availableSize)
{
var font = Font ?? context.DefaultFont;
return new Size2(Width + Padding.Left + Padding.Right, (Height <= 0.0f ? font.LineHeight + 2 : Height) + Padding.Top + Padding.Bottom);
}
public override bool OnPointerDown(IGuiContext context, GuiPointerEventArgs args)
{
SelectionStart = FindNearestGlyphIndex(context, args.Position);
_isCaretVisible = true;
_selectionIndexes.Clear();
_selectionIndexes.Push(SelectionStart);
_startSelectionBox = Text.Length > 0;
return base.OnPointerDown(context, args);
}
public override bool OnPointerMove(IGuiContext context, GuiPointerEventArgs args)
{
if (_startSelectionBox)
{
var selection = FindNearestGlyphIndex(context, args.Position);
if (selection != _selectionIndexes.Peek())
{
if (_selectionIndexes.Count == 1)
{
_selectionIndexes.Push(selection);
}
else if (_selectionIndexes.Last() < _selectionIndexes.Peek())
{
if (selection > _selectionIndexes.Peek()) _selectionIndexes.Pop();
else _selectionIndexes.Push(selection);
}
else
{
if (selection < _selectionIndexes.Peek()) _selectionIndexes.Pop();
else _selectionIndexes.Push(selection);
}
SelectionStart = selection;
}
}
return base.OnPointerMove(context, args);
}
public override bool OnPointerLeave(IGuiContext context, GuiPointerEventArgs args)
{
_startSelectionBox = false;
return base.OnPointerLeave(context, args);
}
public override bool OnPointerUp(IGuiContext context, GuiPointerEventArgs args)
{
_startSelectionBox = false;
return base.OnPointerUp(context, args);
}
private int FindNearestGlyphIndex(IGuiContext context, Point position)
{
var font = Font ?? context.DefaultFont;
var textInfo = GetTextInfo(context, Text, BoundingRectangle, HorizontalAlignment.Centre, VerticalAlignment.Centre);
var i = 0;
foreach (var glyph in font.GetGlyphs(textInfo.Text, textInfo.Position))
{
var fontRegionWidth = glyph.FontRegion?.Width ?? 0;
var glyphMiddle = (int)(glyph.Position.X + fontRegionWidth * 0.5f);
if (position.X >= glyphMiddle)
{
i++;
continue;
}
return i;
}
return i;
}
public override bool OnKeyPressed(IGuiContext context, KeyboardEventArgs args)
{
switch (args.Key)
{
case Keys.Tab:
case Keys.Enter:
return true;
case Keys.Back:
if (Text.Length > 0)
{
if (SelectionStart > 0 && _selectionIndexes.Count <= 1)
{
SelectionStart--;
Text = Text.Remove(SelectionStart, 1);
}
else
{
var start = MathHelper.Min(_selectionIndexes.Last(), _selectionIndexes.Peek());
var end = MathHelper.Max(_selectionIndexes.Last(), _selectionIndexes.Peek());
Text = Text.Remove(start, end - start);
_selectionIndexes.Clear();
}
}
break;
case Keys.Delete:
if (SelectionStart < Text.Length && _selectionIndexes.Count <= 1)
{
Text = Text.Remove(SelectionStart, 1);
}
else if (_selectionIndexes.Count > 1)
{
var start = MathHelper.Min(_selectionIndexes.Last(), _selectionIndexes.Peek());
var end = MathHelper.Max(_selectionIndexes.Last(), _selectionIndexes.Peek());
Text = Text.Remove(start, end - start);
_selectionIndexes.Clear();
}
break;
case Keys.Left:
if (SelectionStart > 0)
{
if (_selectionIndexes.Count > 1)
{
if (_selectionIndexes.Last() < SelectionStart) SelectionStart = _selectionIndexes.Last();
_selectionIndexes.Clear();
}
else
{
SelectionStart--;
}
}
break;
case Keys.Right:
if (SelectionStart < Text.Length)
{
if (_selectionIndexes.Count > 1)
{
if (_selectionIndexes.Last() > SelectionStart) SelectionStart = _selectionIndexes.Last();
_selectionIndexes.Clear();
}
else
{
SelectionStart++;
}
}
break;
case Keys.Home:
SelectionStart = 0;
_selectionIndexes.Clear();
break;
case Keys.End:
SelectionStart = Text.Length;
_selectionIndexes.Clear();
break;
default:
if (args.Character != null)
{
if (_selectionIndexes.Count > 1)
{
var start = MathHelper.Min(_selectionIndexes.Last(), _selectionIndexes.Peek());
var end = MathHelper.Max(_selectionIndexes.Last(), _selectionIndexes.Peek());
Text = Text.Remove(start, end - start);
_selectionIndexes.Clear();
}
Text = Text.Insert(SelectionStart, args.Character.ToString());
SelectionStart++;
}
break;
}
_isCaretVisible = true;
return base.OnKeyPressed(context, args);
}
private const float _caretBlinkRate = 0.53f;
private float _nextCaretBlink = _caretBlinkRate;
private bool _isCaretVisible = true;
private bool _startSelectionBox = false;
private Stack<int> _selectionIndexes = new Stack<int>();
protected override void DrawForeground(IGuiContext context, IGuiRenderer renderer, float deltaSeconds, TextInfo textInfo)
{
if (PasswordCharacter.HasValue)
textInfo = GetTextInfo(context, new string(PasswordCharacter.Value, textInfo.Text.Length), BoundingRectangle, HorizontalAlignment.Centre, VerticalAlignment.Centre);
base.DrawForeground(context, renderer, deltaSeconds, textInfo);
if (IsFocused)
{
var caretRectangle = GetCaretRectangle(textInfo, SelectionStart);
if (_isCaretVisible)
renderer.DrawRectangle((Rectangle)caretRectangle, TextColor);
_nextCaretBlink -= deltaSeconds;
if (_nextCaretBlink <= 0)
{
_isCaretVisible = !_isCaretVisible;
_nextCaretBlink = _caretBlinkRate;
}
if (_selectionIndexes.Count > 1)
{
var start = 0;
var end = 0;
var point = Point2.Zero;
if (_selectionIndexes.Last() > _selectionIndexes.Peek())
{
start = _selectionIndexes.Peek();
end = _selectionIndexes.Last();
point = caretRectangle.Position;
}
else
{
start = _selectionIndexes.Last();
end = _selectionIndexes.Peek();
point = GetCaretRectangle(textInfo, start).Position;
}
var selectionRectangle = textInfo.Font.GetStringRectangle(textInfo.Text.Substring(start, end - start), point);
renderer.FillRectangle((Rectangle)selectionRectangle, Color.Black * 0.25f);
}
}
}
protected override string CreateBoxText(string text, BitmapFont font, float width)
{
return text;
}
private RectangleF GetCaretRectangle(TextInfo textInfo, int index)
{
var caretRectangle = textInfo.Font.GetStringRectangle(textInfo.Text.Substring(0, index), textInfo.Position);
// 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 caretRectangle;
}
}
}