diff --git a/OpenRA.FileFormats/FieldLoader.cs b/OpenRA.FileFormats/FieldLoader.cs index 0c4a56b27c..fc64c526f3 100755 --- a/OpenRA.FileFormats/FieldLoader.cs +++ b/OpenRA.FileFormats/FieldLoader.cs @@ -174,6 +174,15 @@ namespace OpenRA.FileFormats return InvalidValueAction(value, fieldType, fieldName); } + else if (fieldType == typeof(Hotkey)) + { + Hotkey res; + if (Hotkey.TryParse(value, out res)) + return res; + + return InvalidValueAction(value, fieldType, fieldName); + } + else if (fieldType == typeof(WRange)) { WRange res; diff --git a/OpenRA.FileFormats/Graphics/IInputHandler.cs b/OpenRA.FileFormats/Graphics/IInputHandler.cs index 397ebb1c35..b4ab513100 100755 --- a/OpenRA.FileFormats/Graphics/IInputHandler.cs +++ b/OpenRA.FileFormats/Graphics/IInputHandler.cs @@ -66,10 +66,9 @@ namespace OpenRA public struct KeyInput { public KeyInputEvent Event; - public char UnicodeChar; - public string KeyName; + public Keycode Key; public Modifiers Modifiers; - public int VirtKey; public int MultiTapCount; + public char UnicodeChar; } } diff --git a/OpenRA.FileFormats/Hotkey.cs b/OpenRA.FileFormats/Hotkey.cs new file mode 100755 index 0000000000..2bbced90f5 --- /dev/null +++ b/OpenRA.FileFormats/Hotkey.cs @@ -0,0 +1,119 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.FileFormats; + +namespace OpenRA +{ + public struct Hotkey + { + public static Hotkey Invalid = new Hotkey(Keycode.UNKNOWN, Modifiers.None); + + public readonly Keycode Key; + public readonly Modifiers Modifiers; + + public static bool TryParse(string s, out Hotkey result) + { + result = Invalid; + + var parts = s.Split(' '); + if (parts.Length >= 2) + { + if (!Enum.GetNames(typeof(Keycode)).Contains(parts[0])) + return false; + + var modString = s.Substring(s.IndexOf(' ')); + var modParts = modString.Split(',').Select(x => x.Trim()); + if (modParts.Any(p => !Enum.GetNames(typeof(Modifiers)).Contains(p))) + return false; + + var key = (Keycode)Enum.Parse(typeof(Keycode), parts[0]); + var mods = (Modifiers)Enum.Parse(typeof(Modifiers), modString); + + result = new Hotkey(key, mods); + return true; + } + + if (parts.Length == 1) + { + // HACK: Try parsing as a legacy key name + // This is a stop-gap solution to keep backwards + // compatibility while outside code is converted + var i = 0; + for (; i < (int)Keycode.LAST; i++) + if (KeycodeExts.DisplayString((Keycode)i) == parts[0]) + break; + + if (i < (int)Keycode.LAST) + { + result = new Hotkey((Keycode)i, Modifiers.None); + return true; + } + } + + return false; + } + + public static Hotkey FromKeyInput(KeyInput ki) + { + return new Hotkey(ki.Key, ki.Modifiers); + } + + public Hotkey(Keycode virtKey, Modifiers mod) + { + Key = virtKey; + Modifiers = mod; + } + + public static bool operator !=(Hotkey a, Hotkey b) { return !(a == b); } + public static bool operator ==(Hotkey a, Hotkey b) + { + // Unknown keys are never equal + if (a.Key == Keycode.UNKNOWN) + return false; + + return a.Key == b.Key && a.Modifiers == b.Modifiers; + } + + public override int GetHashCode() { return Key.GetHashCode() ^ Modifiers.GetHashCode(); } + + public override bool Equals(object obj) + { + if (obj == null) + return false; + + return (Hotkey)obj == this; + } + + public override string ToString() { return "{0} {1}".F(Key, Modifiers.ToString("F")); } + + public string DisplayString() + { + var ret = KeycodeExts.DisplayString(Key).ToUpper(); + + if (Modifiers.HasModifier(Modifiers.Shift)) + ret = "Shift + " + ret; + + if (Modifiers.HasModifier(Modifiers.Alt)) + ret = "Alt + " + ret; + + if (Modifiers.HasModifier(Modifiers.Ctrl)) + ret = "Ctrl + " + ret; + + if (Modifiers.HasModifier(Modifiers.Meta)) + ret = (Platform.CurrentPlatform == PlatformType.OSX ? "Cmd + " : "Meta + ") + ret; + + return ret; + } + } +} diff --git a/OpenRA.FileFormats/Keycode.cs b/OpenRA.FileFormats/Keycode.cs new file mode 100755 index 0000000000..bba38dbf8e --- /dev/null +++ b/OpenRA.FileFormats/Keycode.cs @@ -0,0 +1,500 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenRA +{ + public enum Keycode + { + UNKNOWN = 0, + FIRST = 0, + BACKSPACE = 8, + TAB = 9, + CLEAR = 12, + RETURN = 13, + PAUSE = 19, + ESCAPE = 27, + SPACE = 32, + EXCLAIM = 33, + QUOTEDBL = 34, + HASH = 35, + DOLLAR = 36, + AMPERSAND = 38, + QUOTE = 39, + LEFTPAREN = 40, + RIGHTPAREN = 41, + ASTERISK = 42, + PLUS = 43, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + NUMBER_0 = 48, + NUMBER_1 = 49, + NUMBER_2 = 50, + NUMBER_3 = 51, + NUMBER_4 = 52, + NUMBER_5 = 53, + NUMBER_6 = 54, + NUMBER_7 = 55, + NUMBER_8 = 56, + NUMBER_9 = 57, + COLON = 58, + SEMICOLON = 59, + LESS = 60, + EQUALS = 61, + GREATER = 62, + QUESTION = 63, + AT = 64, + LEFTBRACKET = 91, + BACKSLASH = 92, + RIGHTBRACKET = 93, + CARET = 94, + UNDERSCORE = 95, + BACKQUOTE = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + DELETE = 127, + WORLD_0 = 160, + WORLD_1 = 161, + WORLD_2 = 162, + WORLD_3 = 163, + WORLD_4 = 164, + WORLD_5 = 165, + WORLD_6 = 166, + WORLD_7 = 167, + WORLD_8 = 168, + WORLD_9 = 169, + WORLD_10 = 170, + WORLD_11 = 171, + WORLD_12 = 172, + WORLD_13 = 173, + WORLD_14 = 174, + WORLD_15 = 175, + WORLD_16 = 176, + WORLD_17 = 177, + WORLD_18 = 178, + WORLD_19 = 179, + WORLD_20 = 180, + WORLD_21 = 181, + WORLD_22 = 182, + WORLD_23 = 183, + WORLD_24 = 184, + WORLD_25 = 185, + WORLD_26 = 186, + WORLD_27 = 187, + WORLD_28 = 188, + WORLD_29 = 189, + WORLD_30 = 190, + WORLD_31 = 191, + WORLD_32 = 192, + WORLD_33 = 193, + WORLD_34 = 194, + WORLD_35 = 195, + WORLD_36 = 196, + WORLD_37 = 197, + WORLD_38 = 198, + WORLD_39 = 199, + WORLD_40 = 200, + WORLD_41 = 201, + WORLD_42 = 202, + WORLD_43 = 203, + WORLD_44 = 204, + WORLD_45 = 205, + WORLD_46 = 206, + WORLD_47 = 207, + WORLD_48 = 208, + WORLD_49 = 209, + WORLD_50 = 210, + WORLD_51 = 211, + WORLD_52 = 212, + WORLD_53 = 213, + WORLD_54 = 214, + WORLD_55 = 215, + WORLD_56 = 216, + WORLD_57 = 217, + WORLD_58 = 218, + WORLD_59 = 219, + WORLD_60 = 220, + WORLD_61 = 221, + WORLD_62 = 222, + WORLD_63 = 223, + WORLD_64 = 224, + WORLD_65 = 225, + WORLD_66 = 226, + WORLD_67 = 227, + WORLD_68 = 228, + WORLD_69 = 229, + WORLD_70 = 230, + WORLD_71 = 231, + WORLD_72 = 232, + WORLD_73 = 233, + WORLD_74 = 234, + WORLD_75 = 235, + WORLD_76 = 236, + WORLD_77 = 237, + WORLD_78 = 238, + WORLD_79 = 239, + WORLD_80 = 240, + WORLD_81 = 241, + WORLD_82 = 242, + WORLD_83 = 243, + WORLD_84 = 244, + WORLD_85 = 245, + WORLD_86 = 246, + WORLD_87 = 247, + WORLD_88 = 248, + WORLD_89 = 249, + WORLD_90 = 250, + WORLD_91 = 251, + WORLD_92 = 252, + WORLD_93 = 253, + WORLD_94 = 254, + WORLD_95 = 255, + KP0 = 256, + KP1 = 257, + KP2 = 258, + KP3 = 259, + KP4 = 260, + KP5 = 261, + KP6 = 262, + KP7 = 263, + KP8 = 264, + KP9 = 265, + KP_PERIOD = 266, + KP_DIVIDE = 267, + KP_MULTIPLY = 268, + KP_MINUS = 269, + KP_PLUS = 270, + KP_ENTER = 271, + KP_EQUALS = 272, + UP = 273, + DOWN = 274, + RIGHT = 275, + LEFT = 276, + INSERT = 277, + HOME = 278, + END = 279, + PAGEUP = 280, + PAGEDOWN = 281, + F1 = 282, + F2 = 283, + F3 = 284, + F4 = 285, + F5 = 286, + F6 = 287, + F7 = 288, + F8 = 289, + F9 = 290, + F10 = 291, + F11 = 292, + F12 = 293, + F13 = 294, + F14 = 295, + F15 = 296, + NUMLOCK = 300, + CAPSLOCK = 301, + SCROLLOCK = 302, + RSHIFT = 303, + LSHIFT = 304, + RCTRL = 305, + LCTRL = 306, + RALT = 307, + LALT = 308, + RMETA = 309, + LMETA = 310, + LSUPER = 311, + RSUPER = 312, + MODE = 313, + COMPOSE = 314, + HELP = 315, + PRINT = 316, + SYSREQ = 317, + BREAK = 318, + MENU = 319, + POWER = 320, + EURO = 321, + UNDO = 322, + LAST + } + + public static class KeycodeExts + { + static readonly Dictionary KeyNames = new Dictionary + { + { Keycode.UNKNOWN, "unknown" }, + { Keycode.BACKSPACE, "backspace" }, + { Keycode.TAB, "tab" }, + { Keycode.CLEAR, "clear" }, + { Keycode.RETURN, "return" }, + { Keycode.PAUSE, "pause" }, + { Keycode.ESCAPE, "escape" }, + { Keycode.SPACE, "space" }, + { Keycode.EXCLAIM, "!" }, + { Keycode.QUOTEDBL, "\"" }, + { Keycode.HASH, "#" }, + { Keycode.DOLLAR, "$" }, + { Keycode.AMPERSAND, "&" }, + { Keycode.QUOTE, "'" }, + { Keycode.LEFTPAREN, "(" }, + { Keycode.RIGHTPAREN, ")" }, + { Keycode.ASTERISK, "*" }, + { Keycode.PLUS, "+" }, + { Keycode.COMMA, "," }, + { Keycode.MINUS, "-" }, + { Keycode.PERIOD, "." }, + { Keycode.SLASH, "/" }, + { Keycode.NUMBER_0, "0" }, + { Keycode.NUMBER_1, "1" }, + { Keycode.NUMBER_2, "2" }, + { Keycode.NUMBER_3, "3" }, + { Keycode.NUMBER_4, "4" }, + { Keycode.NUMBER_5, "5" }, + { Keycode.NUMBER_6, "6" }, + { Keycode.NUMBER_7, "7" }, + { Keycode.NUMBER_8, "8" }, + { Keycode.NUMBER_9, "9" }, + { Keycode.COLON, ":" }, + { Keycode.SEMICOLON, " }," }, + { Keycode.LESS, "<" }, + { Keycode.EQUALS, "=" }, + { Keycode.GREATER, ">" }, + { Keycode.QUESTION, "?" }, + { Keycode.AT, "@" }, + { Keycode.LEFTBRACKET, "[" }, + { Keycode.BACKSLASH, "\\" }, + { Keycode.RIGHTBRACKET, "]" }, + { Keycode.CARET, "^" }, + { Keycode.UNDERSCORE, "_" }, + { Keycode.BACKQUOTE, "`" }, + { Keycode.A, "a" }, + { Keycode.B, "b" }, + { Keycode.C, "c" }, + { Keycode.D, "d" }, + { Keycode.E, "e" }, + { Keycode.F, "f" }, + { Keycode.G, "g" }, + { Keycode.H, "h" }, + { Keycode.I, "i" }, + { Keycode.J, "j" }, + { Keycode.K, "k" }, + { Keycode.L, "l" }, + { Keycode.M, "m" }, + { Keycode.N, "n" }, + { Keycode.O, "o" }, + { Keycode.P, "p" }, + { Keycode.Q, "q" }, + { Keycode.R, "r" }, + { Keycode.S, "s" }, + { Keycode.T, "t" }, + { Keycode.U, "u" }, + { Keycode.V, "v" }, + { Keycode.W, "w" }, + { Keycode.X, "x" }, + { Keycode.Y, "y" }, + { Keycode.Z, "z" }, + { Keycode.DELETE, "delete" }, + { Keycode.WORLD_0, "world 0" }, + { Keycode.WORLD_1, "world 1" }, + { Keycode.WORLD_2, "world 2" }, + { Keycode.WORLD_3, "world 3" }, + { Keycode.WORLD_4, "world 4" }, + { Keycode.WORLD_5, "world 5" }, + { Keycode.WORLD_6, "world 6" }, + { Keycode.WORLD_7, "world 7" }, + { Keycode.WORLD_8, "world 8" }, + { Keycode.WORLD_9, "world 9" }, + { Keycode.WORLD_10, "world 10" }, + { Keycode.WORLD_11, "world 11" }, + { Keycode.WORLD_12, "world 12" }, + { Keycode.WORLD_13, "world 13" }, + { Keycode.WORLD_14, "world 14" }, + { Keycode.WORLD_15, "world 15" }, + { Keycode.WORLD_16, "world 16" }, + { Keycode.WORLD_17, "world 17" }, + { Keycode.WORLD_18, "world 18" }, + { Keycode.WORLD_19, "world 19" }, + { Keycode.WORLD_20, "world 20" }, + { Keycode.WORLD_21, "world 21" }, + { Keycode.WORLD_22, "world 22" }, + { Keycode.WORLD_23, "world 23" }, + { Keycode.WORLD_24, "world 24" }, + { Keycode.WORLD_25, "world 25" }, + { Keycode.WORLD_26, "world 26" }, + { Keycode.WORLD_27, "world 27" }, + { Keycode.WORLD_28, "world 28" }, + { Keycode.WORLD_29, "world 29" }, + { Keycode.WORLD_30, "world 30" }, + { Keycode.WORLD_31, "world 31" }, + { Keycode.WORLD_32, "world 32" }, + { Keycode.WORLD_33, "world 33" }, + { Keycode.WORLD_34, "world 34" }, + { Keycode.WORLD_35, "world 35" }, + { Keycode.WORLD_36, "world 36" }, + { Keycode.WORLD_37, "world 37" }, + { Keycode.WORLD_38, "world 38" }, + { Keycode.WORLD_39, "world 39" }, + { Keycode.WORLD_40, "world 40" }, + { Keycode.WORLD_41, "world 41" }, + { Keycode.WORLD_42, "world 42" }, + { Keycode.WORLD_43, "world 43" }, + { Keycode.WORLD_44, "world 44" }, + { Keycode.WORLD_45, "world 45" }, + { Keycode.WORLD_46, "world 46" }, + { Keycode.WORLD_47, "world 47" }, + { Keycode.WORLD_48, "world 48" }, + { Keycode.WORLD_49, "world 49" }, + { Keycode.WORLD_50, "world 50" }, + { Keycode.WORLD_51, "world 51" }, + { Keycode.WORLD_52, "world 52" }, + { Keycode.WORLD_53, "world 53" }, + { Keycode.WORLD_54, "world 54" }, + { Keycode.WORLD_55, "world 55" }, + { Keycode.WORLD_56, "world 56" }, + { Keycode.WORLD_57, "world 57" }, + { Keycode.WORLD_58, "world 58" }, + { Keycode.WORLD_59, "world 59" }, + { Keycode.WORLD_60, "world 60" }, + { Keycode.WORLD_61, "world 61" }, + { Keycode.WORLD_62, "world 62" }, + { Keycode.WORLD_63, "world 63" }, + { Keycode.WORLD_64, "world 64" }, + { Keycode.WORLD_65, "world 65" }, + { Keycode.WORLD_66, "world 66" }, + { Keycode.WORLD_67, "world 67" }, + { Keycode.WORLD_68, "world 68" }, + { Keycode.WORLD_69, "world 69" }, + { Keycode.WORLD_70, "world 70" }, + { Keycode.WORLD_71, "world 71" }, + { Keycode.WORLD_72, "world 72" }, + { Keycode.WORLD_73, "world 73" }, + { Keycode.WORLD_74, "world 74" }, + { Keycode.WORLD_75, "world 75" }, + { Keycode.WORLD_76, "world 76" }, + { Keycode.WORLD_77, "world 77" }, + { Keycode.WORLD_78, "world 78" }, + { Keycode.WORLD_79, "world 79" }, + { Keycode.WORLD_80, "world 80" }, + { Keycode.WORLD_81, "world 81" }, + { Keycode.WORLD_82, "world 82" }, + { Keycode.WORLD_83, "world 83" }, + { Keycode.WORLD_84, "world 84" }, + { Keycode.WORLD_85, "world 85" }, + { Keycode.WORLD_86, "world 86" }, + { Keycode.WORLD_87, "world 87" }, + { Keycode.WORLD_88, "world 88" }, + { Keycode.WORLD_89, "world 89" }, + { Keycode.WORLD_90, "world 90" }, + { Keycode.WORLD_91, "world 91" }, + { Keycode.WORLD_92, "world 92" }, + { Keycode.WORLD_93, "world 93" }, + { Keycode.WORLD_94, "world 94" }, + { Keycode.WORLD_95, "world 95" }, + { Keycode.KP0, "[0]" }, + { Keycode.KP1, "[1]" }, + { Keycode.KP2, "[2]" }, + { Keycode.KP3, "[3]" }, + { Keycode.KP4, "[4]" }, + { Keycode.KP5, "[5]" }, + { Keycode.KP6, "[6]" }, + { Keycode.KP7, "[7]" }, + { Keycode.KP8, "[8]" }, + { Keycode.KP9, "[9]" }, + { Keycode.KP_PERIOD, "[.]" }, + { Keycode.KP_DIVIDE, "[/]" }, + { Keycode.KP_MULTIPLY, "[*]" }, + { Keycode.KP_MINUS, "[-]" }, + { Keycode.KP_PLUS, "[+]" }, + { Keycode.KP_ENTER, "enter" }, + { Keycode.KP_EQUALS, "equals" }, + { Keycode.UP, "up" }, + { Keycode.DOWN, "down" }, + { Keycode.RIGHT, "right" }, + { Keycode.LEFT, "left" }, + { Keycode.INSERT, "insert" }, + { Keycode.HOME, "home" }, + { Keycode.END, "end" }, + { Keycode.PAGEUP, "page up" }, + { Keycode.PAGEDOWN, "page down" }, + { Keycode.F1, "f1" }, + { Keycode.F2, "f2" }, + { Keycode.F3, "f3" }, + { Keycode.F4, "f4" }, + { Keycode.F5, "f5" }, + { Keycode.F6, "f6" }, + { Keycode.F7, "f7" }, + { Keycode.F8, "f8" }, + { Keycode.F9, "f9" }, + { Keycode.F10, "f10" }, + { Keycode.F11, "f11" }, + { Keycode.F12, "f12" }, + { Keycode.F13, "f13" }, + { Keycode.F14, "f14" }, + { Keycode.F15, "f15" }, + { Keycode.NUMLOCK, "numlock" }, + { Keycode.CAPSLOCK, "caps lock" }, + { Keycode.SCROLLOCK, "scroll lock" }, + { Keycode.RSHIFT, "right shift" }, + { Keycode.LSHIFT, "left shift" }, + { Keycode.RCTRL, "right ctrl" }, + { Keycode.LCTRL, "left ctrl" }, + { Keycode.RALT, "right alt" }, + { Keycode.LALT, "left alt" }, + { Keycode.RMETA, "right meta" }, + { Keycode.LMETA, "left meta" }, + { Keycode.LSUPER, "left super" }, + { Keycode.RSUPER, "right super" }, + { Keycode.MODE, "alt gr" }, + { Keycode.COMPOSE, "compose" }, + { Keycode.HELP, "help" }, + { Keycode.PRINT, "print screen" }, + { Keycode.SYSREQ, "sys req" }, + { Keycode.BREAK, "break" }, + { Keycode.MENU, "menu" }, + { Keycode.POWER, "power" }, + { Keycode.EURO, "euro" }, + { Keycode.UNDO, "undo" } + }; + + public static string DisplayString(Keycode k) + { + var ret = "unknown"; + KeyNames.TryGetValue(k, out ret); + return ret; + } + } +} diff --git a/OpenRA.FileFormats/OpenRA.FileFormats.csproj b/OpenRA.FileFormats/OpenRA.FileFormats.csproj index 09c4962242..ecdf6dfe9b 100644 --- a/OpenRA.FileFormats/OpenRA.FileFormats.csproj +++ b/OpenRA.FileFormats/OpenRA.FileFormats.csproj @@ -151,6 +151,8 @@ + + diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index 0d9f81578c..4000f15f1f 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -148,23 +148,23 @@ namespace OpenRA.GameRules public class KeySettings { - public string CycleBaseKey = "backspace"; - public string ToLastEventKey = "space"; - public string ToSelectionKey = "home"; + public Hotkey CycleBaseKey = new Hotkey(Keycode.BACKSPACE, Modifiers.None); + public Hotkey ToLastEventKey = new Hotkey(Keycode.SPACE, Modifiers.None); + public Hotkey ToSelectionKey = new Hotkey(Keycode.HOME, Modifiers.None); - public string PauseKey = "f9"; - public string SellKey = "f10"; - public string PowerDownKey = "f11"; - public string RepairKey = "f12"; + public Hotkey PauseKey = new Hotkey(Keycode.F9, Modifiers.None); + public Hotkey SellKey = new Hotkey(Keycode.F10, Modifiers.None); + public Hotkey PowerDownKey = new Hotkey(Keycode.F11, Modifiers.None); + public Hotkey RepairKey = new Hotkey(Keycode.F12, Modifiers.None); - public string AttackMoveKey = "a"; - public string StopKey = "s"; - public string ScatterKey = "x"; - public string DeployKey = "f"; - public string StanceCycleKey = "z"; - public string GuardKey = "d"; + public Hotkey AttackMoveKey = new Hotkey(Keycode.A, Modifiers.None); + public Hotkey StopKey = new Hotkey(Keycode.S, Modifiers.None); + public Hotkey ScatterKey = new Hotkey(Keycode.X, Modifiers.None); + public Hotkey DeployKey = new Hotkey(Keycode.F, Modifiers.None); + public Hotkey StanceCycleKey = new Hotkey(Keycode.Z, Modifiers.None); + public Hotkey GuardKey = new Hotkey(Keycode.D, Modifiers.None); - public string CycleTabsKey = "tab"; + public Hotkey CycleTabsKey = new Hotkey(Keycode.TAB, Modifiers.None); } public class IrcSettings diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index eca34a8364..a60f54b7d9 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -236,6 +236,7 @@ + diff --git a/OpenRA.Game/Widgets/ButtonWidget.cs b/OpenRA.Game/Widgets/ButtonWidget.cs index 364e45474a..a14d548487 100644 --- a/OpenRA.Game/Widgets/ButtonWidget.cs +++ b/OpenRA.Game/Widgets/ButtonWidget.cs @@ -18,8 +18,8 @@ namespace OpenRA.Widgets { public class ButtonWidget : Widget { - public Func GetKey = _ => null; - public string Key + public Func GetKey = _ => Hotkey.Invalid; + public Hotkey Key { get { return GetKey(this); } set { GetKey = _ => value; } @@ -91,7 +91,7 @@ namespace OpenRA.Widgets public override bool HandleKeyPress(KeyInput e) { - if (e.KeyName != Key || e.Event != KeyInputEvent.Down) + if (Hotkey.FromKeyInput(e) != Key || e.Event != KeyInputEvent.Down) return false; if (!IsDisabled()) diff --git a/OpenRA.Game/Widgets/ChatEntryWidget.cs b/OpenRA.Game/Widgets/ChatEntryWidget.cs index 0d1ce7bdf8..19c1f0e196 100755 --- a/OpenRA.Game/Widgets/ChatEntryWidget.cs +++ b/OpenRA.Game/Widgets/ChatEntryWidget.cs @@ -49,7 +49,7 @@ namespace OpenRA.Widgets { if (e.Event == KeyInputEvent.Up) return false; - if (e.KeyName == "return" || e.KeyName == "enter" ) + if (e.Key == Keycode.RETURN || e.Key == Keycode.KP_ENTER) { if (composing) { @@ -79,14 +79,14 @@ namespace OpenRA.Widgets if (composing) { - if (e.KeyName == "escape") + if (e.Key == Keycode.ESCAPE) { composing = false; content = ""; YieldKeyboardFocus(); return true; } - else if (e.KeyName == "backspace") + else if (e.Key == Keycode.BACKSPACE) { if (content.Length > 0) content = content.Remove(content.Length - 1); diff --git a/OpenRA.Game/Widgets/HotkeyEntryWidget.cs b/OpenRA.Game/Widgets/HotkeyEntryWidget.cs new file mode 100644 index 0000000000..714b3b1da1 --- /dev/null +++ b/OpenRA.Game/Widgets/HotkeyEntryWidget.cs @@ -0,0 +1,122 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Drawing; +using System.Linq; +using OpenRA.Traits; +using OpenRA.Graphics; + +namespace OpenRA.Widgets +{ + public class HotkeyEntryWidget : Widget + { + public Hotkey Key; + + public int VisualHeight = 1; + public int LeftMargin = 5; + public int RightMargin = 5; + + public Action OnLoseFocus = () => { }; + + public Func IsDisabled = () => false; + public Color TextColor = Color.White; + public Color DisabledColor = Color.Gray; + public string Font = "Regular"; + + public HotkeyEntryWidget() : base() {} + protected HotkeyEntryWidget(HotkeyEntryWidget widget) + : base(widget) + { + Font = widget.Font; + TextColor = widget.TextColor; + DisabledColor = widget.DisabledColor; + VisualHeight = widget.VisualHeight; + } + + public override bool YieldKeyboardFocus() + { + OnLoseFocus(); + return base.YieldKeyboardFocus(); + } + + public override bool HandleMouseInput(MouseInput mi) + { + if (IsDisabled()) + return false; + + if (mi.Event != MouseInputEvent.Down) + return false; + + // Attempt to take keyboard focus + if (!RenderBounds.Contains(mi.Location) || !TakeKeyboardFocus()) + return false; + + return true; + } + + static readonly Keycode[] IgnoreKeys = new Keycode[] + { + Keycode.RSHIFT, Keycode.LSHIFT, + Keycode.RCTRL, Keycode.LCTRL, + Keycode.RALT, Keycode.LALT, + Keycode.RMETA, Keycode.LMETA + }; + + public override bool HandleKeyPress(KeyInput e) + { + if (IsDisabled() || e.Event == KeyInputEvent.Up) + return false; + + if (!HasKeyboardFocus || IgnoreKeys.Contains(e.Key)) + return false; + + Key = Hotkey.FromKeyInput(e); + + return true; + } + + public override void Draw() + { + var apparentText = Key.DisplayString(); + + var font = Game.Renderer.Fonts[Font]; + var pos = RenderOrigin; + + var textSize = font.Measure(apparentText); + + var disabled = IsDisabled(); + var state = disabled ? "textfield-disabled" : + HasKeyboardFocus ? "textfield-focused" : + Ui.MouseOverWidget == this ? "textfield-hover" : + "textfield"; + + WidgetUtils.DrawPanel(state, RenderBounds); + + // Inset text by the margin and center vertically + var textPos = pos + new int2(LeftMargin, (Bounds.Height - textSize.Y) / 2 - VisualHeight); + + // Scissor when the text overflows + if (textSize.X > Bounds.Width - LeftMargin - RightMargin) + { + Game.Renderer.EnableScissor(pos.X + LeftMargin, pos.Y, + Bounds.Width - LeftMargin - RightMargin, Bounds.Bottom); + } + + var color = disabled ? DisabledColor : TextColor; + font.DrawText(apparentText, textPos, color); + + if (textSize.X > Bounds.Width - LeftMargin - RightMargin) + Game.Renderer.DisableScissor(); + } + + public override Widget Clone() { return new HotkeyEntryWidget(this); } + } +} \ No newline at end of file diff --git a/OpenRA.Game/Widgets/TextFieldWidget.cs b/OpenRA.Game/Widgets/TextFieldWidget.cs index cb386340a3..7afcbad503 100644 --- a/OpenRA.Game/Widgets/TextFieldWidget.cs +++ b/OpenRA.Game/Widgets/TextFieldWidget.cs @@ -110,16 +110,16 @@ namespace OpenRA.Widgets if (!HasKeyboardFocus) return false; - if ((e.KeyName == "return" || e.KeyName == "enter") && OnEnterKey()) + if ((e.Key == Keycode.RETURN || e.Key == Keycode.KP_ENTER) && OnEnterKey()) return true; - if (e.KeyName == "tab" && OnTabKey()) + if (e.Key == Keycode.TAB && OnTabKey()) return true; - if (e.KeyName == "escape" && OnEscKey()) + if (e.Key == Keycode.ESCAPE && OnEscKey()) return true; - if (e.KeyName == "left") + if (e.Key == Keycode.LEFT) { if (CursorPosition > 0) CursorPosition--; @@ -127,7 +127,7 @@ namespace OpenRA.Widgets return true; } - if (e.KeyName == "right") + if (e.Key == Keycode.RIGHT) { if (CursorPosition <= Text.Length-1) CursorPosition++; @@ -135,19 +135,19 @@ namespace OpenRA.Widgets return true; } - if (e.KeyName == "home") + if (e.Key == Keycode.HOME) { CursorPosition = 0; return true; } - if (e.KeyName == "end") + if (e.Key == Keycode.END) { CursorPosition = Text.Length; return true; } - if (e.KeyName == "delete") + if (e.Key == Keycode.DELETE) { if (CursorPosition < Text.Length) Text = Text.Remove(CursorPosition, 1); @@ -160,7 +160,7 @@ namespace OpenRA.Widgets public void TypeChar(KeyInput key) { - if (key.KeyName == "backspace" && CursorPosition > 0) + if (key.Key == Keycode.BACKSPACE && CursorPosition > 0) { CursorPosition--; Text = Text.Remove(CursorPosition, 1); diff --git a/OpenRA.Game/Widgets/ViewportControllerWidget.cs b/OpenRA.Game/Widgets/ViewportControllerWidget.cs index 8ee5bc8052..b5ad7128ed 100644 --- a/OpenRA.Game/Widgets/ViewportControllerWidget.cs +++ b/OpenRA.Game/Widgets/ViewportControllerWidget.cs @@ -167,12 +167,12 @@ namespace OpenRA.Widgets public override bool HandleKeyPress(KeyInput e) { - switch (e.KeyName) + switch (e.Key) { - case "up": keyboardDirections = keyboardDirections.Set(ScrollDirection.Up, e.Event == KeyInputEvent.Down); return true; - case "down": keyboardDirections = keyboardDirections.Set(ScrollDirection.Down, e.Event == KeyInputEvent.Down); return true; - case "left": keyboardDirections = keyboardDirections.Set(ScrollDirection.Left, e.Event == KeyInputEvent.Down); return true; - case "right": keyboardDirections = keyboardDirections.Set(ScrollDirection.Right, e.Event == KeyInputEvent.Down); return true; + case Keycode.UP: keyboardDirections = keyboardDirections.Set(ScrollDirection.Up, e.Event == KeyInputEvent.Down); return true; + case Keycode.DOWN: keyboardDirections = keyboardDirections.Set(ScrollDirection.Down, e.Event == KeyInputEvent.Down); return true; + case Keycode.LEFT: keyboardDirections = keyboardDirections.Set(ScrollDirection.Left, e.Event == KeyInputEvent.Down); return true; + case Keycode.RIGHT: keyboardDirections = keyboardDirections.Set(ScrollDirection.Right, e.Event == KeyInputEvent.Down); return true; } return false; diff --git a/OpenRA.Game/Widgets/VqaPlayerWidget.cs b/OpenRA.Game/Widgets/VqaPlayerWidget.cs index 6c6933981f..418ff1eb4f 100644 --- a/OpenRA.Game/Widgets/VqaPlayerWidget.cs +++ b/OpenRA.Game/Widgets/VqaPlayerWidget.cs @@ -108,7 +108,7 @@ namespace OpenRA.Widgets { if (e.Event == KeyInputEvent.Down) { - if (e.KeyName == "escape") + if (e.Key == Keycode.ESCAPE) { Stop(); return true; diff --git a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs index 12ac02159c..27fb1e0b60 100644 --- a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs @@ -169,14 +169,15 @@ namespace OpenRA.Widgets { if (e.Event == KeyInputEvent.Down) { - if (e.KeyName.Length == 1 && char.IsDigit(e.KeyName[0])) + if (e.Key >= Keycode.NUMBER_0 && e.Key <= Keycode.NUMBER_9) { - world.Selection.DoControlGroup(world, worldRenderer, e.KeyName[0] - '0', e.Modifiers, e.MultiTapCount); + var group = (int)e.Key - (int)Keycode.NUMBER_0; + world.Selection.DoControlGroup(world, worldRenderer, group, e.Modifiers, e.MultiTapCount); return true; } // Disable pausing for spectators - else if (e.KeyName == Game.Settings.Keys.PauseKey && world.LocalPlayer != null) + else if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.PauseKey && world.LocalPlayer != null) world.SetPauseState(!world.Paused); } return false; diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/ButtonTooltipLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/ButtonTooltipLogic.cs index 713bdc578b..49277b0643 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/ButtonTooltipLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/ButtonTooltipLogic.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic var labelWidth = Game.Renderer.Fonts[label.Font].Measure(button.TooltipText).X; label.Bounds.Width = labelWidth; - var hotkeyLabel = "({0})".F(button.Key.ToUpperInvariant()); + var hotkeyLabel = "({0})".F(button.Key.DisplayString()); hotkey.GetText = () => hotkeyLabel; hotkey.Bounds.X = labelWidth + 2 * label.Bounds.X; diff --git a/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs b/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs index 41c5be3040..336b9fa97e 100755 --- a/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs +++ b/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs @@ -274,8 +274,10 @@ namespace OpenRA.Mods.Cnc.Widgets public override bool HandleKeyPress(KeyInput e) { - if (e.Event != KeyInputEvent.Down) return false; - if (e.KeyName == Game.Settings.Keys.CycleTabsKey) + if (e.Event != KeyInputEvent.Down) + return false; + + if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.CycleTabsKey) { Sound.PlayNotification(null, "Sounds", "ClickSound", null); SelectNextTab(e.Modifiers.HasModifier(Modifiers.Shift)); diff --git a/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs b/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs index a18a7fb18d..2f658a89b0 100755 --- a/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs +++ b/OpenRA.Mods.RA/Widgets/BuildPaletteWidget.cs @@ -146,13 +146,15 @@ namespace OpenRA.Mods.RA.Widgets public override bool HandleKeyPress(KeyInput e) { - if (e.Event == KeyInputEvent.Up) return false; - if (e.KeyName == Game.Settings.Keys.CycleTabsKey) + if (e.Event == KeyInputEvent.Up) + return false; + + if (Hotkey.FromKeyInput(e) == Game.Settings.Keys.CycleTabsKey) { TabChange(e.Modifiers.HasModifier(Modifiers.Shift)); return true; } - return DoBuildingHotkey(e.KeyName, world); + return DoBuildingHotkey(e, world); } public override bool HandleMouseInput(MouseInput mi) @@ -495,12 +497,12 @@ namespace OpenRA.Mods.RA.Widgets p.ToInt2(), Color.White); } - bool DoBuildingHotkey(string key, World world) + bool DoBuildingHotkey(KeyInput e, World world) { if (!paletteOpen) return false; if (CurrentQueue == null) return false; - var toBuild = CurrentQueue.BuildableItems().FirstOrDefault(b => b.Traits.Get().Hotkey == key); + var toBuild = CurrentQueue.BuildableItems().FirstOrDefault(b => b.Traits.Get().Hotkey == KeycodeExts.DisplayString(e.Key)); if (toBuild != null) { diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index 8587827343..7c40bc2382 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -70,9 +70,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic chatPanel.OnKeyPress = (e) => { if (e.Event == KeyInputEvent.Up) return false; - if (!IsOpen && (e.KeyName == "enter" || e.KeyName == "return") ) + if (!IsOpen && (e.Key == Keycode.RETURN || e.Key == Keycode.KP_ENTER)) { - var shift = e.Modifiers.HasModifier(Modifiers.Shift); var toggle = Game.Settings.Game.TeamChatToggle ; TeamChat = (!toggle && shift) || ( toggle && (TeamChat ^ shift) ); diff --git a/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs index 97793acf60..aedc3103d3 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/SettingsMenuLogic.cs @@ -335,22 +335,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic return true; } - void SetupKeyBinding(ScrollItemWidget keyWidget, string description, Func getValue, Action setValue) + void SetupKeyBinding(ScrollItemWidget keyWidget, string description, Func getValue, Action setValue) { keyWidget.Get("FUNCTION").GetText = () => description; - var textBox = keyWidget.Get("HOTKEY"); - - textBox.Text = getValue(); - textBox.OnLoseFocus = () => - { - textBox.Text.Trim(); - if (textBox.Text.Length == 0) - textBox.Text = getValue(); - else - setValue(textBox.Text); - }; - textBox.OnEnterKey = () => { textBox.YieldKeyboardFocus(); return true; }; + var keyEntry = keyWidget.Get("HOTKEY"); + keyEntry.Key = getValue(); + keyEntry.OnLoseFocus = () => setValue(keyEntry.Key); } static bool ShowRendererDropdown(DropDownButtonWidget dropdown, GraphicSettings s) diff --git a/OpenRA.Mods.RA/Widgets/OrderButtonWidget.cs b/OpenRA.Mods.RA/Widgets/OrderButtonWidget.cs index ecb58a2817..386681042d 100755 --- a/OpenRA.Mods.RA/Widgets/OrderButtonWidget.cs +++ b/OpenRA.Mods.RA/Widgets/OrderButtonWidget.cs @@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA.Widgets public OrderButtonWidget() { GetImage = () => Enabled() ? Pressed() ? "pressed" : "normal" : "disabled"; - GetDescription = () => Key != null ? "{0} ({1})".F(Description, Key.ToUpper()) : Description; + GetDescription = () => Key != Hotkey.Invalid ? "{0} ({1})".F(Description, Key.DisplayString()) : Description; GetLongDesc = () => LongDesc; } diff --git a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs index 0e301778cd..d6ec34dd07 100644 --- a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs +++ b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs @@ -45,37 +45,39 @@ namespace OpenRA.Mods.RA.Widgets bool ProcessInput(KeyInput e) { - if (e.Modifiers == Modifiers.None && e.Event == KeyInputEvent.Down) + if (e.Event == KeyInputEvent.Down) { - if (e.KeyName == Game.Settings.Keys.CycleBaseKey) + var key = Hotkey.FromKeyInput(e); + var ks = Game.Settings.Keys; + if (key == ks.CycleBaseKey) return CycleBases(); - if (e.KeyName == Game.Settings.Keys.ToLastEventKey) + if (key == ks.ToLastEventKey) return ToLastEvent(); - if (e.KeyName == Game.Settings.Keys.ToSelectionKey) + if (key == ks.ToSelectionKey) return ToSelection(); // Put all functions that aren't unit-specific before this line! if (!world.Selection.Actors.Any()) return false; - if (e.KeyName == Game.Settings.Keys.AttackMoveKey) + if (key == ks.AttackMoveKey) return PerformAttackMove(); - if (e.KeyName == Game.Settings.Keys.StopKey) + if (key == ks.StopKey) return PerformStop(); - if (e.KeyName == Game.Settings.Keys.ScatterKey) + if (key == ks.ScatterKey) return PerformScatter(); - if (e.KeyName == Game.Settings.Keys.DeployKey) + if (key == ks.DeployKey) return PerformDeploy(); - if (e.KeyName == Game.Settings.Keys.StanceCycleKey) + if (key == ks.StanceCycleKey) return PerformStanceCycle(); - if (e.KeyName == Game.Settings.Keys.GuardKey) + if (key == ks.GuardKey) return PerformGuard(); } diff --git a/OpenRA.Renderer.SdlCommon/MultiTapDetection.cs b/OpenRA.Renderer.SdlCommon/MultiTapDetection.cs index 33bc647f28..45d9312bdf 100644 --- a/OpenRA.Renderer.SdlCommon/MultiTapDetection.cs +++ b/OpenRA.Renderer.SdlCommon/MultiTapDetection.cs @@ -14,8 +14,8 @@ using OpenRA.FileFormats; public static class MultiTapDetection { - static Cache keyHistoryCache = - new Cache(_ => new TapHistory(DateTime.Now - TimeSpan.FromSeconds(1))); + static Cache keyHistoryCache = + new Cache(_ => new TapHistory(DateTime.Now - TimeSpan.FromSeconds(1))); static Cache clickHistoryCache = new Cache(_ => new TapHistory(DateTime.Now - TimeSpan.FromSeconds(1))); @@ -29,12 +29,12 @@ public static class MultiTapDetection return clickHistoryCache[button].LastTapCount(); } - public static int DetectFromKeyboard(string key) + public static int DetectFromKeyboard(Keycode key) { return keyHistoryCache[key].GetTapCount(int2.Zero); } - public static int InfoFromKeyboard(string key) + public static int InfoFromKeyboard(Keycode key) { return keyHistoryCache[key].LastTapCount(); } diff --git a/OpenRA.Renderer.SdlCommon/SdlInput.cs b/OpenRA.Renderer.SdlCommon/SdlInput.cs index 05a417bba6..80badd2568 100644 --- a/OpenRA.Renderer.SdlCommon/SdlInput.cs +++ b/OpenRA.Renderer.SdlCommon/SdlInput.cs @@ -102,17 +102,23 @@ namespace OpenRA.Renderer.SdlCommon } case Sdl.SDL_KEYDOWN: + case Sdl.SDL_KEYUP: { - var keyName = Sdl.SDL_GetKeyName(e.key.keysym.sym); + var keyCode = (Keycode)e.key.keysym.sym; + var type = e.type == Sdl.SDL_KEYDOWN ? + KeyInputEvent.Down : KeyInputEvent.Up; + + var tapCount = e.type == Sdl.SDL_KEYDOWN ? + MultiTapDetection.DetectFromKeyboard(keyCode) : + MultiTapDetection.InfoFromKeyboard(keyCode); var keyEvent = new KeyInput { - Event = KeyInputEvent.Down, + Event = type, + Key = keyCode, Modifiers = mods, UnicodeChar = (char)e.key.keysym.unicode, - KeyName = Sdl.SDL_GetKeyName(e.key.keysym.sym), - VirtKey = e.key.keysym.sym, - MultiTapCount = MultiTapDetection.DetectFromKeyboard(keyName) + MultiTapCount = tapCount }; // Special case workaround for windows users @@ -126,23 +132,6 @@ namespace OpenRA.Renderer.SdlCommon break; } - - case Sdl.SDL_KEYUP: - { - var keyName = Sdl.SDL_GetKeyName(e.key.keysym.sym); - var keyEvent = new KeyInput - { - Event = KeyInputEvent.Up, - Modifiers = mods, - UnicodeChar = (char)e.key.keysym.unicode, - KeyName = Sdl.SDL_GetKeyName(e.key.keysym.sym), - VirtKey = e.key.keysym.sym, - MultiTapCount = MultiTapDetection.InfoFromKeyboard(keyName) - }; - - inputHandler.OnKeyInput(keyEvent); - break; - } } } diff --git a/mods/ra/chrome/settings.yaml b/mods/ra/chrome/settings.yaml index 6c4ed0af40..84056ab4df 100644 --- a/mods/ra/chrome/settings.yaml +++ b/mods/ra/chrome/settings.yaml @@ -321,11 +321,10 @@ Background@SETTINGS_MENU: X:10 Width:200 Height:25 - TextField@HOTKEY: + HotkeyEntry@HOTKEY: X:250 Width:139 Height:25 - MaxLength:16 Label@KEYS_UNITCOMMANDSHEADLINE: X:0 Y:130 @@ -348,11 +347,10 @@ Background@SETTINGS_MENU: X:10 Width:200 Height:25 - TextField@HOTKEY: + HotkeyEntry@HOTKEY: X:250 Width:139 Height:25 - MaxLength:16 Container@DEBUG_PANE: X:37 Y:100