Added text selection and copy support to TextFieldWidget.

Use Shift and navigation key (cursor, home, end) to select a portion of
text, and replace/delete/cut as appropriate.
Also provides support for selection with mouse (click and drag)
This commit is contained in:
Joe Alam
2018-04-07 17:45:11 +00:00
committed by reaperrr
parent b5893d4c6d
commit 7221c29d9b
7 changed files with 148 additions and 10 deletions

View File

@@ -45,6 +45,11 @@ namespace OpenRA.Mods.Common.Widgets
public Color TextColor = ChromeMetrics.Get<Color>("TextfieldColor");
public Color TextColorDisabled = ChromeMetrics.Get<Color>("TextfieldColorDisabled");
public Color TextColorInvalid = ChromeMetrics.Get<Color>("TextfieldColorInvalid");
public Color TextColorHighlight = ChromeMetrics.Get<Color>("TextfieldColorHighlight");
protected int selectionStartIndex = -1;
protected int selectionEndIndex = -1;
protected bool mouseSelectionActive = false;
public TextFieldWidget()
{
@@ -60,6 +65,7 @@ namespace OpenRA.Mods.Common.Widgets
TextColor = widget.TextColor;
TextColorDisabled = widget.TextColorDisabled;
TextColorInvalid = widget.TextColorInvalid;
TextColorHighlight = widget.TextColorHighlight;
VisualHeight = widget.VisualHeight;
IsDisabled = widget.IsDisabled;
}
@@ -81,15 +87,35 @@ namespace OpenRA.Mods.Common.Widgets
if (IsDisabled())
return false;
if (mi.Event != MouseInputEvent.Down)
if (mouseSelectionActive)
{
if (mi.Event == MouseInputEvent.Up)
{
mouseSelectionActive = false;
return true;
}
else if (mi.Event != MouseInputEvent.Move)
return false;
}
else if (mi.Event != MouseInputEvent.Down)
return false;
// Attempt to take keyboard focus
if (!RenderBounds.Contains(mi.Location) || !TakeKeyboardFocus())
return false;
mouseSelectionActive = true;
ResetBlinkCycle();
var cachedCursorPos = CursorPosition;
CursorPosition = ClosestCursorPosition(mi.Location.X);
if (mi.Modifiers.HasModifier(Modifiers.Shift) || (mi.Event == MouseInputEvent.Move && mouseSelectionActive))
HandleSelectionUpdate(cachedCursorPos, CursorPosition);
else
ClearSelection();
return true;
}
@@ -147,7 +173,8 @@ namespace OpenRA.Mods.Common.Widgets
var isOSX = Platform.CurrentPlatform == PlatformType.OSX;
switch (e.Key) {
switch (e.Key)
{
case Keycode.RETURN:
case Keycode.KP_ENTER:
if (OnEnterKey())
@@ -160,6 +187,7 @@ namespace OpenRA.Mods.Common.Widgets
break;
case Keycode.ESCAPE:
ClearSelection();
if (OnEscKey())
return true;
break;
@@ -173,12 +201,19 @@ namespace OpenRA.Mods.Common.Widgets
ResetBlinkCycle();
if (CursorPosition > 0)
{
var cachedCurrentCursorPos = CursorPosition;
if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Alt)))
CursorPosition = GetPrevWhitespaceIndex();
else if (isOSX && e.Modifiers.HasModifier(Modifiers.Meta))
CursorPosition = 0;
else
CursorPosition--;
if (e.Modifiers.HasModifier(Modifiers.Shift))
HandleSelectionUpdate(cachedCurrentCursorPos, CursorPosition);
else
ClearSelection();
}
break;
@@ -187,23 +222,41 @@ namespace OpenRA.Mods.Common.Widgets
ResetBlinkCycle();
if (CursorPosition <= Text.Length - 1)
{
var cachedCurrentCursorPos = CursorPosition;
if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Alt)))
CursorPosition = GetNextWhitespaceIndex();
else if (isOSX && e.Modifiers.HasModifier(Modifiers.Meta))
CursorPosition = Text.Length;
else
CursorPosition++;
if (e.Modifiers.HasModifier(Modifiers.Shift))
HandleSelectionUpdate(cachedCurrentCursorPos, CursorPosition);
else
ClearSelection();
}
break;
case Keycode.HOME:
ResetBlinkCycle();
if (e.Modifiers.HasModifier(Modifiers.Shift))
HandleSelectionUpdate(CursorPosition, 0);
else
ClearSelection();
CursorPosition = 0;
break;
case Keycode.END:
ResetBlinkCycle();
if (e.Modifiers.HasModifier(Modifiers.Shift))
HandleSelectionUpdate(CursorPosition, Text.Length);
else
ClearSelection();
CursorPosition = Text.Length;
break;
@@ -234,6 +287,7 @@ namespace OpenRA.Mods.Common.Widgets
{
Text = Text.Substring(CursorPosition);
CursorPosition = 0;
ClearSelection();
OnTextEdited();
}
@@ -242,20 +296,35 @@ namespace OpenRA.Mods.Common.Widgets
case Keycode.X:
ResetBlinkCycle();
if (((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Meta))) &&
!string.IsNullOrEmpty(Text))
!string.IsNullOrEmpty(Text) && selectionStartIndex != -1)
{
Game.Renderer.SetClipboardText(Text);
Text = Text.Remove(0);
CursorPosition = 0;
var lowestIndex = selectionStartIndex < selectionEndIndex ? selectionStartIndex : selectionEndIndex;
var highestIndex = selectionStartIndex < selectionEndIndex ? selectionEndIndex : selectionStartIndex;
Game.Renderer.SetClipboardText(Text.Substring(lowestIndex, highestIndex - lowestIndex));
RemoveSelectedText();
OnTextEdited();
}
break;
case Keycode.C:
ResetBlinkCycle();
if (((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Meta)))
&& !string.IsNullOrEmpty(Text) && selectionStartIndex != -1)
{
var lowestIndex = selectionStartIndex < selectionEndIndex ? selectionStartIndex : selectionEndIndex;
var highestIndex = selectionStartIndex < selectionEndIndex ? selectionEndIndex : selectionStartIndex;
Game.Renderer.SetClipboardText(Text.Substring(lowestIndex, highestIndex - lowestIndex));
}
break;
case Keycode.DELETE:
// cmd+delete is equivalent to ctrl+k on non-osx
ResetBlinkCycle();
if (CursorPosition < Text.Length)
if (selectionStartIndex != -1)
RemoveSelectedText();
else if (CursorPosition < Text.Length)
{
if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Alt)))
Text = Text.Substring(0, CursorPosition) + Text.Substring(GetNextWhitespaceIndex());
@@ -272,7 +341,9 @@ namespace OpenRA.Mods.Common.Widgets
case Keycode.BACKSPACE:
// cmd+backspace is equivalent to ctrl+u on non-osx
ResetBlinkCycle();
if (CursorPosition > 0)
if (selectionStartIndex != -1)
RemoveSelectedText();
else if (CursorPosition > 0)
{
if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Alt)))
{
@@ -298,6 +369,10 @@ namespace OpenRA.Mods.Common.Widgets
case Keycode.V:
ResetBlinkCycle();
if (selectionStartIndex != -1)
RemoveSelectedText();
if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Meta)))
{
var clipboardText = Game.Renderer.GetClipboardText();
@@ -313,10 +388,18 @@ namespace OpenRA.Mods.Common.Widgets
}
break;
case Keycode.A:
// Ctrl+A as Select-All, or Cmd+A on OSX
if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Meta)))
{
ClearSelection();
HandleSelectionUpdate(0, Text.Length);
}
break;
default:
break;
}
}
return true;
}
@@ -326,6 +409,9 @@ namespace OpenRA.Mods.Common.Widgets
if (!HasKeyboardFocus || IsDisabled())
return false;
if (selectionStartIndex != -1)
RemoveSelectedText();
if (MaxLength > 0 && Text.Length >= MaxLength)
return true;
@@ -337,11 +423,44 @@ namespace OpenRA.Mods.Common.Widgets
Text = Text.Insert(CursorPosition, text.Substring(0, pasteLength));
CursorPosition += pasteLength;
ClearSelection();
OnTextEdited();
return true;
}
void HandleSelectionUpdate(int prevCursorPos, int newCursorPos)
{
// If selection index is -1, there's no selection already open so create one
if (selectionStartIndex == -1)
selectionStartIndex = prevCursorPos;
selectionEndIndex = newCursorPos;
if (selectionStartIndex == selectionEndIndex)
ClearSelection();
}
void ClearSelection()
{
selectionStartIndex = -1;
selectionEndIndex = -1;
}
void RemoveSelectedText()
{
if (selectionStartIndex != -1)
{
var lowestIndex = selectionStartIndex < selectionEndIndex ? selectionStartIndex : selectionEndIndex;
var highestIndex = selectionStartIndex < selectionEndIndex ? selectionEndIndex : selectionStartIndex;
Text = Text.Remove(lowestIndex, highestIndex - lowestIndex);
ClearSelection();
CursorPosition = lowestIndex;
}
}
protected int blinkCycle = 10;
protected bool showCursor = true;
@@ -383,7 +502,8 @@ namespace OpenRA.Mods.Common.Widgets
new Rectangle(pos.X, pos.Y, Bounds.Width, Bounds.Height));
// Inset text by the margin and center vertically
var textPos = pos + new int2(LeftMargin, (Bounds.Height - textSize.Y) / 2 - VisualHeight);
var verticalMargin = (Bounds.Height - textSize.Y) / 2 - VisualHeight;
var textPos = pos + new int2(LeftMargin, verticalMargin);
// Right align when editing and scissor when the text overflows
if (textSize.X > Bounds.Width - LeftMargin - RightMargin)
@@ -395,6 +515,18 @@ namespace OpenRA.Mods.Common.Widgets
Bounds.Width - LeftMargin - RightMargin, Bounds.Bottom));
}
// Draw the highlight around the selected area
if (selectionStartIndex != -1)
{
var visualSelectionStartIndex = selectionStartIndex < selectionEndIndex ? selectionStartIndex : selectionEndIndex;
var visualSelectionEndIndex = selectionStartIndex < selectionEndIndex ? selectionEndIndex : selectionStartIndex;
var highlightStartX = font.Measure(apparentText.Substring(0, visualSelectionStartIndex)).X;
var highlightEndX = font.Measure(apparentText.Substring(0, visualSelectionEndIndex)).X;
WidgetUtils.FillRectWithColor(
new Rectangle(textPos.X + highlightStartX, textPos.Y, highlightEndX - highlightStartX, Bounds.Height - (verticalMargin * 2)), TextColorHighlight);
}
var color =
disabled ? TextColorDisabled
: IsValid() ? TextColor