diff --git a/OpenRA.Game/Graphics/IGraphicsDevice.cs b/OpenRA.Game/Graphics/IGraphicsDevice.cs index 64d46048ca..36cd2d8abe 100644 --- a/OpenRA.Game/Graphics/IGraphicsDevice.cs +++ b/OpenRA.Game/Graphics/IGraphicsDevice.cs @@ -61,6 +61,7 @@ namespace OpenRA Bitmap TakeScreenshot(); void PumpInput(IInputHandler inputHandler); string GetClipboardText(); + bool SetClipboardText(string text); void DrawPrimitives(PrimitiveType type, int firstVertex, int numVertices); void SetLineWidth(float width); diff --git a/OpenRA.Game/Renderer.cs b/OpenRA.Game/Renderer.cs index 25206015a4..6e01b39813 100644 --- a/OpenRA.Game/Renderer.cs +++ b/OpenRA.Game/Renderer.cs @@ -267,5 +267,10 @@ namespace OpenRA { return Device.GetClipboardText(); } + + public bool SetClipboardText(string text) + { + return Device.SetClipboardText(text); + } } } diff --git a/OpenRA.Mods.Common/Widgets/TextFieldWidget.cs b/OpenRA.Mods.Common/Widgets/TextFieldWidget.cs index f18d6872bd..615927eb43 100644 --- a/OpenRA.Mods.Common/Widgets/TextFieldWidget.cs +++ b/OpenRA.Mods.Common/Widgets/TextFieldWidget.cs @@ -69,6 +69,12 @@ namespace OpenRA.Mods.Common.Widgets return base.YieldKeyboardFocus(); } + protected void ResetBlinkCycle() + { + blinkCycle = 10; + showCursor = true; + } + public override bool HandleMouseInput(MouseInput mi) { if (IsDisabled()) @@ -81,15 +87,14 @@ namespace OpenRA.Mods.Common.Widgets if (!RenderBounds.Contains(mi.Location) || !TakeKeyboardFocus()) return false; - blinkCycle = 10; - showCursor = true; + ResetBlinkCycle(); CursorPosition = ClosestCursorPosition(mi.Location.X); return true; } protected virtual string GetApparentText() { return text; } - public int ClosestCursorPosition(int x) + int ClosestCursorPosition(int x) { var apparentText = GetApparentText(); var font = Game.Renderer.Fonts[Font]; @@ -113,6 +118,23 @@ namespace OpenRA.Mods.Common.Widgets return minIndex; } + int GetPrevWhitespaceIndex() + { + return Text.Substring(0, CursorPosition).TrimEnd().LastIndexOf(' ') + 1; + } + + int GetNextWhitespaceIndex() + { + var substr = Text.Substring(CursorPosition); + var substrTrimmed = substr.TrimStart(); + var trimmedSpaces = substr.Length - substrTrimmed.Length; + var nextWhitespace = substrTrimmed.IndexOf(' '); + if (nextWhitespace == -1) + return Text.Length; + + return CursorPosition + trimmedSpaces + nextWhitespace; + } + public override bool HandleKeyPress(KeyInput e) { if (IsDisabled() || e.Event == KeyInputEvent.Up) @@ -122,83 +144,179 @@ namespace OpenRA.Mods.Common.Widgets if (!HasKeyboardFocus) return false; - if ((e.Key == Keycode.RETURN || e.Key == Keycode.KP_ENTER) && OnEnterKey()) - return true; + var isOSX = Platform.CurrentPlatform == PlatformType.OSX; - if (e.Key == Keycode.TAB && OnTabKey()) - return true; + switch (e.Key) { + case Keycode.RETURN: + case Keycode.KP_ENTER: + if (OnEnterKey()) + return true; + break; - if (e.Key == Keycode.ESCAPE && OnEscKey()) - return true; + case Keycode.TAB: + if (OnTabKey()) + return true; + break; - if (e.Key == Keycode.LALT && OnAltKey()) - return true; + case Keycode.ESCAPE: + if (OnEscKey()) + return true; + break; - if (e.Key == Keycode.LEFT) - { - if (CursorPosition > 0) - CursorPosition--; + case Keycode.LALT: + if (OnAltKey()) + return true; + break; - return true; - } + case Keycode.LEFT: + ResetBlinkCycle(); + if (CursorPosition > 0) + { + 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.Key == Keycode.RIGHT) - { - if (CursorPosition <= Text.Length - 1) - CursorPosition++; + break; - return true; - } + case Keycode.RIGHT: + ResetBlinkCycle(); + if (CursorPosition <= Text.Length - 1) + { + 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.Key == Keycode.HOME) - { - CursorPosition = 0; - return true; - } + break; - if (e.Key == Keycode.END) - { - CursorPosition = Text.Length; - return true; - } + case Keycode.HOME: + ResetBlinkCycle(); + CursorPosition = 0; + break; - if (e.Key == Keycode.DELETE) - { - if (CursorPosition < Text.Length) - { - Text = Text.Remove(CursorPosition, 1); - OnTextEdited(); + case Keycode.END: + ResetBlinkCycle(); + CursorPosition = Text.Length; + break; + + case Keycode.D: + if (e.Modifiers.HasModifier(Modifiers.Ctrl) && CursorPosition < Text.Length) + { + Text = Text.Remove(CursorPosition, 1); + OnTextEdited(); + } + + break; + + case Keycode.K: + // ctrl+k is equivalent to cmd+delete on osx (but also works on osx) + ResetBlinkCycle(); + if (e.Modifiers.HasModifier(Modifiers.Ctrl) && CursorPosition < Text.Length) + { + Text = Text.Remove(CursorPosition); + OnTextEdited(); + } + + break; + + case Keycode.U: + // ctrl+u is equivalent to cmd+backspace on osx + ResetBlinkCycle(); + if (!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl) && CursorPosition > 0) + { + Text = Text.Substring(CursorPosition); + CursorPosition = 0; + OnTextEdited(); + } + + break; + + case Keycode.X: + ResetBlinkCycle(); + if (((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Meta))) && + !string.IsNullOrEmpty(Text)) + { + Game.Renderer.SetClipboardText(Text); + Text = Text.Remove(0); + CursorPosition = 0; + OnTextEdited(); + } + + break; + + case Keycode.DELETE: + // cmd+delete is equivalent to ctrl+k on non-osx + ResetBlinkCycle(); + 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()); + else if (isOSX && e.Modifiers.HasModifier(Modifiers.Meta)) + Text = Text.Remove(CursorPosition); + else + Text = Text.Remove(CursorPosition, 1); + + OnTextEdited(); + } + + break; + + case Keycode.BACKSPACE: + // cmd+backspace is equivalent to ctrl+u on non-osx + ResetBlinkCycle(); + if (CursorPosition > 0) + { + if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Alt))) + { + var prevWhitespace = GetPrevWhitespaceIndex(); + Text = Text.Substring(0, prevWhitespace) + Text.Substring(CursorPosition); + CursorPosition = prevWhitespace; + } + else if (isOSX && e.Modifiers.HasModifier(Modifiers.Meta)) + { + Text = Text.Substring(CursorPosition); + CursorPosition = 0; + } + else + { + CursorPosition--; + Text = Text.Remove(CursorPosition, 1); + } + + OnTextEdited(); + } + + break; + + case Keycode.V: + ResetBlinkCycle(); + if ((!isOSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || (isOSX && e.Modifiers.HasModifier(Modifiers.Meta))) + { + var clipboardText = Game.Renderer.GetClipboardText(); + + // Take only the first line of the clipboard contents + var nl = clipboardText.IndexOf('\n'); + if (nl > 0) + clipboardText = clipboardText.Substring(0, nl); + + clipboardText = clipboardText.Trim(); + if (clipboardText.Length > 0) + HandleTextInput(clipboardText); + } + + break; + + default: + break; } - return true; - } - - if (e.Key == Keycode.BACKSPACE && CursorPosition > 0) - { - CursorPosition--; - Text = Text.Remove(CursorPosition, 1); - OnTextEdited(); - return true; - } - - if (e.Key == Keycode.V && - ((Platform.CurrentPlatform != PlatformType.OSX && e.Modifiers.HasModifier(Modifiers.Ctrl)) || - (Platform.CurrentPlatform == PlatformType.OSX && e.Modifiers.HasModifier(Modifiers.Meta)))) - { - var clipboardText = Game.Renderer.GetClipboardText(); - - // Take only the first line of the clipboard contents - var nl = clipboardText.IndexOf('\n'); - if (nl > 0) - clipboardText = clipboardText.Substring(0, nl); - - clipboardText = clipboardText.Trim(); - if (clipboardText.Length > 0) - HandleTextInput(clipboardText); - - return true; - } - return true; } @@ -291,4 +409,4 @@ namespace OpenRA.Mods.Common.Widgets public override Widget Clone() { return new TextFieldWidget(this); } } -} \ No newline at end of file +} diff --git a/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs b/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs index edcfcf5bcf..51fbbfb5f0 100644 --- a/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs +++ b/OpenRA.Platforms.Default/Sdl2GraphicsDevice.cs @@ -382,6 +382,12 @@ namespace OpenRA.Platforms.Default return input.GetClipboardText(); } + public bool SetClipboardText(string text) + { + VerifyThreadAffinity(); + return input.SetClipboardText(text); + } + public IVertexBuffer CreateVertexBuffer(int size) { VerifyThreadAffinity(); diff --git a/OpenRA.Platforms.Default/Sdl2Input.cs b/OpenRA.Platforms.Default/Sdl2Input.cs index 2582876ffc..48c078306a 100644 --- a/OpenRA.Platforms.Default/Sdl2Input.cs +++ b/OpenRA.Platforms.Default/Sdl2Input.cs @@ -20,6 +20,7 @@ namespace OpenRA.Platforms.Default MouseButton lastButtonBits = (MouseButton)0; public string GetClipboardText() { return SDL.SDL_GetClipboardText(); } + public bool SetClipboardText(string text) { return SDL.SDL_SetClipboardText(text) == 0; } static MouseButton MakeButton(byte b) { @@ -177,4 +178,4 @@ namespace OpenRA.Platforms.Default ErrorHandler.CheckGlError(); } } -} \ No newline at end of file +} diff --git a/OpenRA.Platforms.Null/NullGraphicsDevice.cs b/OpenRA.Platforms.Null/NullGraphicsDevice.cs index 57c3b8d159..401310bf4e 100644 --- a/OpenRA.Platforms.Null/NullGraphicsDevice.cs +++ b/OpenRA.Platforms.Null/NullGraphicsDevice.cs @@ -42,6 +42,7 @@ namespace OpenRA.Platforms.Null public Bitmap TakeScreenshot() { return new Bitmap(1, 1); } public string GetClipboardText() { return ""; } + public bool SetClipboardText(string text) { return false; } public void PumpInput(IInputHandler ih) { Game.HasInputFocus = false;