Skip to content

Commit

Permalink
Merge pull request #1091 from swoolcock/platform-input-manager
Browse files Browse the repository at this point in the history
Platform-specific Input Manager
  • Loading branch information
peppy authored Nov 15, 2017
2 parents c95b935 + cf73a05 commit b128579
Show file tree
Hide file tree
Showing 6 changed files with 283 additions and 172 deletions.
318 changes: 154 additions & 164 deletions osu.Framework/Graphics/UserInterface/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using osu.Framework.Graphics.Colour;
using osu.Framework.Platform;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Timing;

namespace osu.Framework.Graphics.UserInterface
Expand Down Expand Up @@ -70,36 +71,43 @@ public class TextBox : TabbableContainer, IHasCurrentValue<string>

public TextBox()
{
Masking = true;
CornerRadius = 3;

AddRange(new Drawable[]
// TextBoxes currently require their own top-level PlatformInputManager, as InputManagers
// will not propagate events through other InputManagers.
// Once this restriction has been addressed, we can utilise a single instance of PlatformInputManager
// at the Game level, and TextBoxes need only implement IKeyBindingHandler<PlatformAction>.
Child = new PlatformInputManager
{
Background = new Box
{
Colour = BackgroundUnfocused,
RelativeSizeAxes = Axes.Both,
},
TextContainer = new Container
Masking = true,
CornerRadius = 3,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Position = new Vector2(LeftRightPadding, 0),
Children = new[]
Background = new Box
{
Placeholder = CreatePlaceholder(),
Caret = new DrawableCaret(),
TextFlow = new FillFlowContainer
Colour = BackgroundUnfocused,
RelativeSizeAxes = Axes.Both,
},
TextContainer = new KeyBindingContainer(this)
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Position = new Vector2(LeftRightPadding, 0),
Children = new[]
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Placeholder = CreatePlaceholder(),
Caret = new DrawableCaret(),
TextFlow = new FillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
},
},
},
},
});
}
};

Current.ValueChanged += newValue => { Text = newValue; };
}
Expand Down Expand Up @@ -262,6 +270,104 @@ private int getCharacterClosestTo(Vector2 pos)

private Cached cursorAndLayout = new Cached();

private bool handleAction(PlatformAction action)
{
int? amount = null;

switch (action.ActionType)
{
// Clipboard
case PlatformActionType.Cut:
case PlatformActionType.Copy:
if (string.IsNullOrEmpty(SelectedText) || !AllowClipboardExport) return true;

clipboard?.SetText(SelectedText);
if (action.ActionType == PlatformActionType.Cut)
removeCharacterOrSelection();
return true;

case PlatformActionType.Paste:
//the text may get pasted into the hidden textbox, so we don't need any direct clipboard interaction here.
string pending = textInput?.GetPendingText();

if (string.IsNullOrEmpty(pending))
pending = clipboard?.GetText();

insertString(pending);
return true;

case PlatformActionType.SelectAll:
selectionStart = 0;
selectionEnd = text.Length;
cursorAndLayout.Invalidate();
return true;

// Cursor Manipulation
case PlatformActionType.CharNext:
if (!HandleLeftRightArrows) return false;
amount = 1;
break;

case PlatformActionType.CharPrevious:
if (!HandleLeftRightArrows) return false;
amount = -1;
break;

case PlatformActionType.LineEnd:
amount = text.Length;
break;

case PlatformActionType.LineStart:
amount = -text.Length;
break;

case PlatformActionType.WordNext:
{
int searchStart = MathHelper.Clamp(selectionEnd, 0, Text.Length - 1);
while (searchStart < Text.Length && text[searchStart] == ' ')
searchStart++;
int nextSpace = text.IndexOf(' ', searchStart);
amount = (nextSpace >= 0 ? nextSpace : text.Length) - selectionEnd;
}
break;

case PlatformActionType.WordPrevious:
{
int searchStart = MathHelper.Clamp(selectionEnd - 2, 0, Text.Length - 1);
while (searchStart > 0 && text[searchStart] == ' ')
searchStart--;
int lastSpace = text.LastIndexOf(' ', searchStart);
amount = lastSpace > 0 ? -(selectionEnd - lastSpace - 1) : -selectionEnd;
}
break;
}

if (amount.HasValue)
{
switch (action.ActionMethod)
{
case PlatformActionMethod.Move:
resetSelection();
moveSelection(amount.Value, false);
break;

case PlatformActionMethod.Select:
moveSelection(amount.Value, true);
break;

case PlatformActionMethod.Delete:
if (selectionLength == 0)
selectionEnd = MathHelper.Clamp(selectionStart + amount.Value, 0, text.Length);
if (selectionLength > 0)
removeCharacterOrSelection();
break;
}
return true;
}

return false;
}

private void moveSelection(int offset, bool expand)
{
if (textInput?.ImeActive == true) return;
Expand Down Expand Up @@ -486,81 +592,26 @@ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)

if (ReadOnly) return true;

if (state.Keyboard.AltPressed)
if (state.Keyboard.AltPressed || state.Keyboard.ControlPressed || state.Keyboard.SuperPressed)
return false;

switch (args.Key)
{
case Key.Left:
case Key.Right:
case Key.Delete:
case Key.BackSpace:
case Key.Home:
case Key.End:
return false;

case Key.Escape:
GetContainingInputManager().ChangeFocus(null);
return true;

case Key.Tab:
return base.OnKeyDown(state, args);
case Key.End:
moveSelection(text.Length, state.Keyboard.ShiftPressed);
return true;
case Key.Home:
moveSelection(-text.Length, state.Keyboard.ShiftPressed);
return true;
case Key.Left:
{
if (!HandleLeftRightArrows) return false;

if (selectionEnd == 0)
{
//we only clear if you aren't holding shift
if (!state.Keyboard.ShiftPressed)
resetSelection();
return true;
}

int amount = 1;
if (state.Keyboard.ControlPressed)
{
int lastSpace = text.LastIndexOf(' ', Math.Max(0, selectionEnd - 2));
if (lastSpace >= 0)
{
//if you have something selected and shift is not held down
//A selection reset is required to select a word inside the current selection
if (!state.Keyboard.ShiftPressed)
resetSelection();
amount = selectionEnd - lastSpace - 1;
}
else
amount = selectionEnd;
}

moveSelection(-amount, state.Keyboard.ShiftPressed);
return true;
}
case Key.Right:
{
if (!HandleLeftRightArrows) return false;

if (selectionEnd == text.Length)
{
if (!state.Keyboard.ShiftPressed)
resetSelection();
return true;
}

int amount = 1;
if (state.Keyboard.ControlPressed)
{
int nextSpace = text.IndexOf(' ', selectionEnd + 1);
if (nextSpace >= 0)
{
if (!state.Keyboard.ShiftPressed)
resetSelection();
amount = nextSpace - selectionEnd;
}
else
amount = text.Length - selectionEnd;
}

moveSelection(amount, state.Keyboard.ShiftPressed);
return true;
}
case Key.KeypadEnter:
case Key.Enter:
if (HasFocus)
Expand All @@ -576,81 +627,6 @@ protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
OnCommit?.Invoke(this, true);
}
return true;
case Key.Delete:
if (selectionLength == 0)
{
if (text.Length == selectionStart)
return true;

if (state.Keyboard.ControlPressed)
{
int spacePos = selectionStart;
while (text[spacePos] == ' ' && spacePos < text.Length)
spacePos++;

spacePos = MathHelper.Clamp(text.IndexOf(' ', spacePos), 0, text.Length);
selectionEnd = spacePos;

if (selectionStart == 0 && spacePos == 0)
selectionEnd = text.Length;

if (selectionLength == 0)
return true;
}
else
{
//we're deleting in front of the cursor, so move the cursor forward once first
selectionStart = selectionEnd = selectionStart + 1;
}
}

removeCharacterOrSelection();
return true;
case Key.Back:
if (selectionLength == 0 && state.Keyboard.ControlPressed)
{
int spacePos = selectionLeft >= 2 ? Math.Max(0, text.LastIndexOf(' ', selectionLeft - 2) + 1) : 0;
selectionStart = spacePos;
}

removeCharacterOrSelection();
return true;
}

if (state.Keyboard.ControlPressed)
{
//handling of function keys
switch (args.Key)
{
case Key.A:
selectionStart = 0;
selectionEnd = text.Length;
cursorAndLayout.Invalidate();
return true;
case Key.C:
if (string.IsNullOrEmpty(SelectedText) || !AllowClipboardExport) return true;

clipboard?.SetText(SelectedText);
return true;
case Key.X:
if (string.IsNullOrEmpty(SelectedText) || !AllowClipboardExport) return true;

clipboard?.SetText(SelectedText);
removeCharacterOrSelection();
return true;
case Key.V:

//the text may get pasted into the hidden textbox, so we don't need any direct clipboard interaction here.
string pending = textInput?.GetPendingText();

if (string.IsNullOrEmpty(pending))
pending = clipboard?.GetText();

insertString(pending);
return true;
}

return false;
}

return true;
Expand Down Expand Up @@ -901,5 +877,19 @@ public DrawableCaret()
};
}
}

private class KeyBindingContainer : Container, IKeyBindingHandler<PlatformAction>
{
private readonly TextBox textBox;

public KeyBindingContainer(TextBox textBox)
{
this.textBox = textBox;
}

public bool OnPressed(PlatformAction action) => textBox.HasFocus && textBox.handleAction(action);

public bool OnReleased(PlatformAction action) => false;
}
}
}
Loading

0 comments on commit b128579

Please sign in to comment.