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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user