From f08a15c2828c7ef852db7b4f73b68a280e3bc4f9 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Tue, 17 Oct 2017 21:56:16 +1030 Subject: [PATCH 1/7] Create platform-specific input manager --- osu.Framework/Game.cs | 6 +- .../Graphics/UserInterface/TextBox.cs | 83 +++++++++++++------ osu.Framework/Input/PlatformInputManager.cs | 20 +++++ osu.Framework/Input/UserInputManager.cs | 7 +- osu.Framework/Platform/GameHost.cs | 14 +++- osu.Framework/Platform/MacOS/MacOSGameHost.cs | 11 +++ osu.Framework/osu.Framework.csproj | 1 + 7 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 osu.Framework/Input/PlatformInputManager.cs diff --git a/osu.Framework/Game.cs b/osu.Framework/Game.cs index 6ff39813a4..182901d73e 100644 --- a/osu.Framework/Game.cs +++ b/osu.Framework/Game.cs @@ -20,7 +20,7 @@ namespace osu.Framework { - public abstract class Game : Container, IKeyBindingHandler + public abstract class Game : Container, IKeyBindingHandler, IKeyBindingHandler { public GameWindow Window => Host?.Window; @@ -218,6 +218,10 @@ public bool OnPressed(FrameworkAction action) public bool OnReleased(FrameworkAction action) => false; + public bool OnPressed(PlatformAction action) => false; + + public bool OnReleased(PlatformAction action) => false; + public void Exit() { Host.Exit(); diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs index 6428bfccab..65b84c60eb 100644 --- a/osu.Framework/Graphics/UserInterface/TextBox.cs +++ b/osu.Framework/Graphics/UserInterface/TextBox.cs @@ -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 @@ -70,36 +71,39 @@ public class TextBox : TabbableContainer, IHasCurrentValue public TextBox() { - Masking = true; - CornerRadius = 3; - - AddRange(new Drawable[] + 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 + { + Colour = BackgroundUnfocused, + RelativeSizeAxes = Axes.Both, + }, + TextContainer = new KeyBindingContainer(this) { - Placeholder = CreatePlaceholder(), - Caret = new DrawableCaret(), - TextFlow = new FillFlowContainer + 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; }; } @@ -901,5 +905,36 @@ public DrawableCaret() }; } } + + private class KeyBindingContainer : Container, IKeyBindingHandler + { + private readonly TextBox textBox; + + public KeyBindingContainer(TextBox textBox) + { + this.textBox = textBox; + } + + public bool OnPressed(PlatformAction action) + { + if (!textBox.HasFocus) return false; + + switch (action) + { + case PlatformAction.Cut: + Console.WriteLine("Pressed Cut"); + break; + case PlatformAction.Copy: + Console.WriteLine("Pressed Copy"); + break; + case PlatformAction.Paste: + Console.WriteLine("Pressed Paste"); + break; + } + return true; + } + + public bool OnReleased(PlatformAction action) => false; + } } } diff --git a/osu.Framework/Input/PlatformInputManager.cs b/osu.Framework/Input/PlatformInputManager.cs new file mode 100644 index 0000000000..8bf4bea064 --- /dev/null +++ b/osu.Framework/Input/PlatformInputManager.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Input.Bindings; + +namespace osu.Framework.Input +{ + public class PlatformInputManager : KeyBindingInputManager + { + public override IEnumerable DefaultKeyBindings => Host.PlatformKeyBindings; + } + + public enum PlatformAction + { + Cut, + Copy, + Paste + } +} diff --git a/osu.Framework/Input/UserInputManager.cs b/osu.Framework/Input/UserInputManager.cs index 4717bc56f3..b5a2632312 100644 --- a/osu.Framework/Input/UserInputManager.cs +++ b/osu.Framework/Input/UserInputManager.cs @@ -11,6 +11,8 @@ namespace osu.Framework.Input { public class UserInputManager : KeyBindingInputManager { + private readonly Drawable handler; + protected override IEnumerable InputHandlers => Host.AvailableInputHandlers; public override IEnumerable DefaultKeyBindings => new[] @@ -21,12 +23,13 @@ public class UserInputManager : KeyBindingInputManager new KeyBinding(new[] { InputKey.Alt, InputKey.Enter }, FrameworkAction.ToggleFullscreen), }; - public UserInputManager() + public UserInputManager(Drawable handler = null) { + this.handler = handler; UseParentState = false; } - protected override IEnumerable KeyBindingInputQueue => new[] { Child }.Concat(base.KeyBindingInputQueue); + protected override IEnumerable KeyBindingInputQueue => new[] { handler ?? Child }.Concat(base.KeyBindingInputQueue); } public enum FrameworkAction diff --git a/osu.Framework/Platform/GameHost.cs b/osu.Framework/Platform/GameHost.cs index f8efdd0092..aded8e341b 100644 --- a/osu.Framework/Platform/GameHost.cs +++ b/osu.Framework/Platform/GameHost.cs @@ -20,6 +20,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL; using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Handlers; using osu.Framework.Localisation; using osu.Framework.Logging; @@ -82,6 +83,8 @@ public virtual Task SendMessageAsync(IpcMessage message) public Storage Storage { get; protected set; } + public PlatformInputManager PlatformInputManager { get; private set; } + /// /// If capslock is enabled on the system, false if not overwritten by a subclass /// @@ -402,7 +405,14 @@ private void resetInputHandlers() private void bootstrapSceneGraph(Game game) { - var root = new UserInputManager { Child = game }; + var root = new UserInputManager(game) + { + Child = PlatformInputManager = new PlatformInputManager + { + Child = game, + RelativeSizeAxes = Axes.Both + } + }; Dependencies.Cache(root); Dependencies.Cache(game); @@ -543,6 +553,8 @@ private void setVSyncMode() public IEnumerable AvailableInputHandlers { get; private set; } + public virtual IEnumerable PlatformKeyBindings => new KeyBinding[0]; + public abstract ITextInputSource GetTextInput(); #region IDisposable Support diff --git a/osu.Framework/Platform/MacOS/MacOSGameHost.cs b/osu.Framework/Platform/MacOS/MacOSGameHost.cs index 33301592d6..387a6edecd 100644 --- a/osu.Framework/Platform/MacOS/MacOSGameHost.cs +++ b/osu.Framework/Platform/MacOS/MacOSGameHost.cs @@ -1,6 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; + namespace osu.Framework.Platform.MacOS { public class MacOSGameHost : DesktopGameHost @@ -21,5 +25,12 @@ internal MacOSGameHost(string gameName, bool bindIPC = false) protected override Storage GetStorage(string baseName) => new MacOSStorage(baseName); public override Clipboard GetClipboard() => new MacOSClipboard(); + + public override IEnumerable PlatformKeyBindings => new[] + { + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.X }), PlatformAction.Cut), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.C }), PlatformAction.Copy), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.V }), PlatformAction.Paste) + }; } } diff --git a/osu.Framework/osu.Framework.csproj b/osu.Framework/osu.Framework.csproj index 7848efced4..d234c04910 100644 --- a/osu.Framework/osu.Framework.csproj +++ b/osu.Framework/osu.Framework.csproj @@ -252,6 +252,7 @@ + From ee36c0a237d6e15ac3964dc5f7e8e3232e7bdea7 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 18 Oct 2017 19:30:27 +1030 Subject: [PATCH 2/7] Rework TextBox to use platform-specific actions --- .../Graphics/UserInterface/TextBox.cs | 261 +++++++----------- osu.Framework/Input/PlatformInputManager.cs | 30 +- osu.Framework/Platform/GameHost.cs | 26 +- osu.Framework/Platform/MacOS/MacOSGameHost.cs | 25 +- 4 files changed, 177 insertions(+), 165 deletions(-) diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs index 65b84c60eb..5923c8382f 100644 --- a/osu.Framework/Graphics/UserInterface/TextBox.cs +++ b/osu.Framework/Graphics/UserInterface/TextBox.cs @@ -266,6 +266,98 @@ 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 nextSpace = text.IndexOf(' ', Math.Min(Text.Length - 1, selectionEnd) + 1); + amount = (nextSpace >= 0 ? nextSpace : text.Length) - selectionEnd; + } + break; + + case PlatformActionType.WordPrevious: + { + int lastSpace = text.LastIndexOf(' ', Math.Max(0, selectionEnd - 2)); + 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; @@ -490,81 +582,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) @@ -580,81 +617,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; @@ -915,24 +877,7 @@ public KeyBindingContainer(TextBox textBox) this.textBox = textBox; } - public bool OnPressed(PlatformAction action) - { - if (!textBox.HasFocus) return false; - - switch (action) - { - case PlatformAction.Cut: - Console.WriteLine("Pressed Cut"); - break; - case PlatformAction.Copy: - Console.WriteLine("Pressed Copy"); - break; - case PlatformAction.Paste: - Console.WriteLine("Pressed Paste"); - break; - } - return true; - } + public bool OnPressed(PlatformAction action) => textBox.HasFocus && textBox.handleAction(action); public bool OnReleased(PlatformAction action) => false; } diff --git a/osu.Framework/Input/PlatformInputManager.cs b/osu.Framework/Input/PlatformInputManager.cs index 8bf4bea064..3aa3b08f50 100644 --- a/osu.Framework/Input/PlatformInputManager.cs +++ b/osu.Framework/Input/PlatformInputManager.cs @@ -11,10 +11,36 @@ public class PlatformInputManager : KeyBindingInputManager public override IEnumerable DefaultKeyBindings => Host.PlatformKeyBindings; } - public enum PlatformAction + public struct PlatformAction + { + public PlatformActionType ActionType; + public PlatformActionMethod? ActionMethod; + + public PlatformAction(PlatformActionType actionType, PlatformActionMethod? actionMethod = null) + { + ActionType = actionType; + ActionMethod = actionMethod; + } + } + + public enum PlatformActionType { Cut, Copy, - Paste + Paste, + SelectAll, + CharPrevious, + CharNext, + WordPrevious, + WordNext, + LineStart, + LineEnd + } + + public enum PlatformActionMethod + { + Move, + Select, + Delete } } diff --git a/osu.Framework/Platform/GameHost.cs b/osu.Framework/Platform/GameHost.cs index aded8e341b..b6ac38d97f 100644 --- a/osu.Framework/Platform/GameHost.cs +++ b/osu.Framework/Platform/GameHost.cs @@ -553,8 +553,6 @@ private void setVSyncMode() public IEnumerable AvailableInputHandlers { get; private set; } - public virtual IEnumerable PlatformKeyBindings => new KeyBinding[0]; - public abstract ITextInputSource GetTextInput(); #region IDisposable Support @@ -588,5 +586,29 @@ public void Dispose() } #endregion + + public virtual IEnumerable PlatformKeyBindings => new[] + { + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.X }), new PlatformAction(PlatformActionType.Cut)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.C }), new PlatformAction(PlatformActionType.Copy)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.V }), new PlatformAction(PlatformActionType.Paste)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.A }), new PlatformAction(PlatformActionType.SelectAll)), + new KeyBinding(InputKey.Left, new PlatformAction(PlatformActionType.CharPrevious, PlatformActionMethod.Move)), + new KeyBinding(InputKey.Right, new PlatformAction(PlatformActionType.CharNext, PlatformActionMethod.Move)), + new KeyBinding(InputKey.BackSpace, new PlatformAction(PlatformActionType.CharPrevious, PlatformActionMethod.Delete)), + new KeyBinding(InputKey.Delete, new PlatformAction(PlatformActionType.CharNext, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Shift, InputKey.Left }), new PlatformAction(PlatformActionType.CharPrevious, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Shift, InputKey.Right }), new PlatformAction(PlatformActionType.CharNext, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.Left }), new PlatformAction(PlatformActionType.WordPrevious, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.Right }), new PlatformAction(PlatformActionType.WordNext, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.BackSpace}), new PlatformAction(PlatformActionType.WordPrevious, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.Delete }), new PlatformAction(PlatformActionType.WordNext, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }), new PlatformAction(PlatformActionType.WordPrevious, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }), new PlatformAction(PlatformActionType.WordNext, PlatformActionMethod.Select)), + new KeyBinding(InputKey.Home, new PlatformAction(PlatformActionType.LineStart, PlatformActionMethod.Move)), + new KeyBinding(InputKey.End, new PlatformAction(PlatformActionType.LineEnd, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Shift, InputKey.Home }), new PlatformAction(PlatformActionType.LineStart, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Shift, InputKey.End }), new PlatformAction(PlatformActionType.LineEnd, PlatformActionMethod.Select)), + }; } } diff --git a/osu.Framework/Platform/MacOS/MacOSGameHost.cs b/osu.Framework/Platform/MacOS/MacOSGameHost.cs index 387a6edecd..2514ff167d 100644 --- a/osu.Framework/Platform/MacOS/MacOSGameHost.cs +++ b/osu.Framework/Platform/MacOS/MacOSGameHost.cs @@ -28,9 +28,28 @@ internal MacOSGameHost(string gameName, bool bindIPC = false) public override IEnumerable PlatformKeyBindings => new[] { - new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.X }), PlatformAction.Cut), - new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.C }), PlatformAction.Copy), - new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.V }), PlatformAction.Paste) + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.X }), new PlatformAction(PlatformActionType.Cut)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.C }), new PlatformAction(PlatformActionType.Copy)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.V }), new PlatformAction(PlatformActionType.Paste)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.A }), new PlatformAction(PlatformActionType.SelectAll)), + new KeyBinding(InputKey.Left, new PlatformAction(PlatformActionType.CharPrevious, PlatformActionMethod.Move)), + new KeyBinding(InputKey.Right, new PlatformAction(PlatformActionType.CharNext, PlatformActionMethod.Move)), + new KeyBinding(InputKey.BackSpace, new PlatformAction(PlatformActionType.CharPrevious, PlatformActionMethod.Delete)), + new KeyBinding(InputKey.Delete, new PlatformAction(PlatformActionType.CharNext, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Shift, InputKey.Left }), new PlatformAction(PlatformActionType.CharPrevious, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Shift, InputKey.Right }), new PlatformAction(PlatformActionType.CharNext, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Alt, InputKey.Left }), new PlatformAction(PlatformActionType.WordPrevious, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Alt, InputKey.Right }), new PlatformAction(PlatformActionType.WordNext, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Alt, InputKey.BackSpace}), new PlatformAction(PlatformActionType.WordPrevious, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Alt, InputKey.Delete }), new PlatformAction(PlatformActionType.WordNext, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Alt, InputKey.Shift, InputKey.Left }), new PlatformAction(PlatformActionType.WordPrevious, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Alt, InputKey.Shift, InputKey.Right }), new PlatformAction(PlatformActionType.WordNext, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.Left }), new PlatformAction(PlatformActionType.LineStart, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.Right }), new PlatformAction(PlatformActionType.LineEnd, PlatformActionMethod.Move)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.BackSpace }), new PlatformAction(PlatformActionType.LineStart, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.Delete }), new PlatformAction(PlatformActionType.LineEnd, PlatformActionMethod.Delete)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.Shift, InputKey.Left }), new PlatformAction(PlatformActionType.LineStart, PlatformActionMethod.Select)), + new KeyBinding(new KeyCombination(new[] { InputKey.Win, InputKey.Shift, InputKey.Right }), new PlatformAction(PlatformActionType.LineEnd, PlatformActionMethod.Select)), }; } } From 587c42ea6e9156617dc939a133cfada5733deda6 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 18 Oct 2017 20:23:33 +1030 Subject: [PATCH 3/7] Add repeat support for actions, and some doco --- .../Input/Bindings/KeyBindingInputManager.cs | 23 ++++++++++++------- osu.Framework/Input/PlatformInputManager.cs | 8 +++++++ osu.Framework/Platform/GameHost.cs | 4 ++++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/osu.Framework/Input/Bindings/KeyBindingInputManager.cs b/osu.Framework/Input/Bindings/KeyBindingInputManager.cs index f2e1b82e5c..dc8427ea42 100644 --- a/osu.Framework/Input/Bindings/KeyBindingInputManager.cs +++ b/osu.Framework/Input/Bindings/KeyBindingInputManager.cs @@ -45,6 +45,12 @@ protected KeyBindingInputManager(SimultaneousBindingMode simultaneousMode = Simu /// protected virtual IEnumerable KeyBindingInputQueue => InputQueue; + /// + /// Override to enable or disable sending of repeated actions (disabled by default). + /// Each repeated action will have its own pressed/released event pair. + /// + protected virtual bool SendRepeats => false; + protected override bool PropagateWheel(IEnumerable drawables, InputState state) { if (base.PropagateWheel(drawables, state)) return true; @@ -59,18 +65,18 @@ protected override bool PropagateWheel(IEnumerable drawables, InputSta InputKey key = state.Mouse.WheelDelta > 0 ? InputKey.MouseWheelUp : InputKey.MouseWheelDown; - return handleNewPressed(state, key) | handleNewReleased(clonedState, key); + return handleNewPressed(state, key, false) | handleNewReleased(clonedState, key); } protected override bool PropagateMouseDown(IEnumerable drawables, InputState state, MouseDownEventArgs args) => - base.PropagateMouseDown(drawables, state, args) || handleNewPressed(state, KeyCombination.FromMouseButton(args.Button)); + base.PropagateMouseDown(drawables, state, args) || handleNewPressed(state, KeyCombination.FromMouseButton(args.Button), false); protected override bool PropagateMouseUp(IEnumerable drawables, InputState state, MouseUpEventArgs args) => base.PropagateMouseUp(drawables, state, args) || handleNewReleased(state, KeyCombination.FromMouseButton(args.Button)); protected override bool PropagateKeyDown(IEnumerable drawables, InputState state, KeyDownEventArgs args) { - if (args.Repeat) + if (args.Repeat && !SendRepeats) { if (pressedBindings.Count > 0) return true; @@ -78,19 +84,19 @@ protected override bool PropagateKeyDown(IEnumerable drawables, InputS return base.PropagateKeyDown(drawables, state, args); } - return base.PropagateKeyDown(drawables, state, args) || handleNewPressed(state, KeyCombination.FromKey(args.Key)); + return base.PropagateKeyDown(drawables, state, args) || handleNewPressed(state, KeyCombination.FromKey(args.Key), args.Repeat); } protected override bool PropagateKeyUp(IEnumerable drawables, InputState state, KeyUpEventArgs args) => base.PropagateKeyUp(drawables, state, args) || handleNewReleased(state, KeyCombination.FromKey(args.Key)); - private bool handleNewPressed(InputState state, InputKey newKey) + private bool handleNewPressed(InputState state, InputKey newKey, bool repeat) { var pressedCombination = KeyCombination.FromInputState(state); bool handled = false; - - var newlyPressed = KeyBindings.Except(pressedBindings).Where(m => + var bindings = repeat ? KeyBindings : KeyBindings.Except(pressedBindings); + var newlyPressed = bindings.Where(m => m.KeyCombination.Keys.Contains(newKey) // only handle bindings matching current key (not required for correct logic) && m.KeyCombination.IsPressed(pressedCombination)); @@ -101,7 +107,8 @@ private bool handleNewPressed(InputState state, InputKey newKey) // we want to always handle bindings with more keys before bindings with less. newlyPressed = newlyPressed.OrderByDescending(b => b.KeyCombination.Keys.Count()).ToList(); - pressedBindings.AddRange(newlyPressed); + if (!repeat) + pressedBindings.AddRange(newlyPressed); foreach (var newBinding in newlyPressed) { diff --git a/osu.Framework/Input/PlatformInputManager.cs b/osu.Framework/Input/PlatformInputManager.cs index 3aa3b08f50..9a0fc6bb2b 100644 --- a/osu.Framework/Input/PlatformInputManager.cs +++ b/osu.Framework/Input/PlatformInputManager.cs @@ -6,9 +6,17 @@ namespace osu.Framework.Input { + /// + /// Provides actions that are expected to have different key bindings per platform. + /// The framework will always contain one top-level instance of this class, but extra instances + /// can be created to handle events that should trigger specifically on a focused drawable. + /// Will send repeat events by default. + /// public class PlatformInputManager : KeyBindingInputManager { public override IEnumerable DefaultKeyBindings => Host.PlatformKeyBindings; + + protected override bool SendRepeats => true; } public struct PlatformAction diff --git a/osu.Framework/Platform/GameHost.cs b/osu.Framework/Platform/GameHost.cs index b6ac38d97f..5fc2f0cb87 100644 --- a/osu.Framework/Platform/GameHost.cs +++ b/osu.Framework/Platform/GameHost.cs @@ -587,6 +587,10 @@ public void Dispose() #endregion + /// + /// Defines the platform-specific key bindings that will be used by . + /// Should be overridden per-platform to provide native key bindings. + /// public virtual IEnumerable PlatformKeyBindings => new[] { new KeyBinding(new KeyCombination(new[] { InputKey.Control, InputKey.X }), new PlatformAction(PlatformActionType.Cut)), From c26ea06d4001153da67f113a9c22e3d9f4619fd3 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 18 Oct 2017 21:05:34 +1030 Subject: [PATCH 4/7] Make next/previous word shortcuts skip over multiple spaces --- osu.Framework/Graphics/UserInterface/TextBox.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs index 5923c8382f..3cf832ff17 100644 --- a/osu.Framework/Graphics/UserInterface/TextBox.cs +++ b/osu.Framework/Graphics/UserInterface/TextBox.cs @@ -319,15 +319,21 @@ private bool handleAction(PlatformAction action) case PlatformActionType.WordNext: { - int nextSpace = text.IndexOf(' ', Math.Min(Text.Length - 1, selectionEnd) + 1); + int searchStart = Math.Min(Text.Length - 1, selectionEnd); + while (searchStart < Text.Length && text[searchStart] == ' ') + searchStart++; + int nextSpace = text.IndexOf(' ', searchStart); amount = (nextSpace >= 0 ? nextSpace : text.Length) - selectionEnd; } break; case PlatformActionType.WordPrevious: { - int lastSpace = text.LastIndexOf(' ', Math.Max(0, selectionEnd - 2)); - amount = lastSpace >= 0 ? -(selectionEnd - lastSpace - 1) : -selectionEnd; + int searchStart = Math.Max(0, selectionEnd - 2); + while (searchStart > 0 && text[searchStart] == ' ') + searchStart--; + int lastSpace = text.LastIndexOf(' ', searchStart); + amount = lastSpace > 0 ? -(selectionEnd - lastSpace - 1) : -selectionEnd; } break; } From 7f96a0f353df3dfc7239cf23fb6555ba17a0795f Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 18 Oct 2017 21:52:01 +1030 Subject: [PATCH 5/7] Fix potential out of bounds exception --- osu.Framework/Graphics/UserInterface/TextBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs index 3cf832ff17..50097959bf 100644 --- a/osu.Framework/Graphics/UserInterface/TextBox.cs +++ b/osu.Framework/Graphics/UserInterface/TextBox.cs @@ -319,7 +319,7 @@ private bool handleAction(PlatformAction action) case PlatformActionType.WordNext: { - int searchStart = Math.Min(Text.Length - 1, selectionEnd); + int searchStart = MathHelper.Clamp(selectionEnd, 0, Text.Length - 1); while (searchStart < Text.Length && text[searchStart] == ' ') searchStart++; int nextSpace = text.IndexOf(' ', searchStart); @@ -329,7 +329,7 @@ private bool handleAction(PlatformAction action) case PlatformActionType.WordPrevious: { - int searchStart = Math.Max(0, selectionEnd - 2); + int searchStart = MathHelper.Clamp(selectionEnd - 2, 0, Text.Length - 1); while (searchStart > 0 && text[searchStart] == ' ') searchStart--; int lastSpace = text.LastIndexOf(' ', searchStart); From d33dd253886a85c7bf6011765620ae506a453b8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Nov 2017 11:52:28 +0900 Subject: [PATCH 6/7] Remove unnecessary game-level interface addition This will likely be added back in the future when we have better support for nested InputManager types --- osu.Framework/Game.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Framework/Game.cs b/osu.Framework/Game.cs index 182901d73e..6ff39813a4 100644 --- a/osu.Framework/Game.cs +++ b/osu.Framework/Game.cs @@ -20,7 +20,7 @@ namespace osu.Framework { - public abstract class Game : Container, IKeyBindingHandler, IKeyBindingHandler + public abstract class Game : Container, IKeyBindingHandler { public GameWindow Window => Host?.Window; @@ -218,10 +218,6 @@ public bool OnPressed(FrameworkAction action) public bool OnReleased(FrameworkAction action) => false; - public bool OnPressed(PlatformAction action) => false; - - public bool OnReleased(PlatformAction action) => false; - public void Exit() { Host.Exit(); From dc482e06ef87c6fb152727fa4175b1fe64de1caa Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Thu, 9 Nov 2017 19:23:57 +1030 Subject: [PATCH 7/7] Revert unnecessary changes now that the PlatformInputManager is no longer used at the Game level Also added a comment as to why TextBoxes have their own PlatformInputManager --- osu.Framework/Graphics/UserInterface/TextBox.cs | 4 ++++ osu.Framework/Input/UserInputManager.cs | 7 ++----- osu.Framework/Platform/GameHost.cs | 11 +---------- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Framework/Graphics/UserInterface/TextBox.cs b/osu.Framework/Graphics/UserInterface/TextBox.cs index 50097959bf..428bc7e49c 100644 --- a/osu.Framework/Graphics/UserInterface/TextBox.cs +++ b/osu.Framework/Graphics/UserInterface/TextBox.cs @@ -71,6 +71,10 @@ public class TextBox : TabbableContainer, IHasCurrentValue public TextBox() { + // 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. Child = new PlatformInputManager { Masking = true, diff --git a/osu.Framework/Input/UserInputManager.cs b/osu.Framework/Input/UserInputManager.cs index b5a2632312..4717bc56f3 100644 --- a/osu.Framework/Input/UserInputManager.cs +++ b/osu.Framework/Input/UserInputManager.cs @@ -11,8 +11,6 @@ namespace osu.Framework.Input { public class UserInputManager : KeyBindingInputManager { - private readonly Drawable handler; - protected override IEnumerable InputHandlers => Host.AvailableInputHandlers; public override IEnumerable DefaultKeyBindings => new[] @@ -23,13 +21,12 @@ public class UserInputManager : KeyBindingInputManager new KeyBinding(new[] { InputKey.Alt, InputKey.Enter }, FrameworkAction.ToggleFullscreen), }; - public UserInputManager(Drawable handler = null) + public UserInputManager() { - this.handler = handler; UseParentState = false; } - protected override IEnumerable KeyBindingInputQueue => new[] { handler ?? Child }.Concat(base.KeyBindingInputQueue); + protected override IEnumerable KeyBindingInputQueue => new[] { Child }.Concat(base.KeyBindingInputQueue); } public enum FrameworkAction diff --git a/osu.Framework/Platform/GameHost.cs b/osu.Framework/Platform/GameHost.cs index cb73f29b90..2e715be97d 100644 --- a/osu.Framework/Platform/GameHost.cs +++ b/osu.Framework/Platform/GameHost.cs @@ -84,8 +84,6 @@ public virtual Task SendMessageAsync(IpcMessage message) public Storage Storage { get; protected set; } - public PlatformInputManager PlatformInputManager { get; private set; } - /// /// If capslock is enabled on the system, false if not overwritten by a subclass /// @@ -408,14 +406,7 @@ private void resetInputHandlers() private void bootstrapSceneGraph(Game game) { - var root = new UserInputManager(game) - { - Child = PlatformInputManager = new PlatformInputManager - { - Child = game, - RelativeSizeAxes = Axes.Both - } - }; + var root = new UserInputManager { Child = game }; Dependencies.Cache(root); Dependencies.Cache(game);