Add a Hotkey class for user-configurable keys. Fixes #3779.
Users can now define and use hotkeys that include modifiers (ctrl/meta/shift/alt).
This commit is contained in:
@@ -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;
|
||||
|
||||
119
OpenRA.FileFormats/Hotkey.cs
Executable file
119
OpenRA.FileFormats/Hotkey.cs
Executable file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ namespace OpenRA
|
||||
{
|
||||
public enum Keycode
|
||||
{
|
||||
UNDEFINED = -1,
|
||||
UNKNOWN = 0,
|
||||
FIRST = 0,
|
||||
BACKSPACE = 8,
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
<Compile Include="Graphics\R8Reader.cs" />
|
||||
<Compile Include="Graphics\TileSetRenderer.cs" />
|
||||
<Compile Include="Keycode.cs" />
|
||||
<Compile Include="Hotkey.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
<Compile Include="Traits\Player\PlayerHighlightPalette.cs" />
|
||||
<Compile Include="Traits\World\ScreenMap.cs" />
|
||||
<Compile Include="Traits\World\ActorMap.cs" />
|
||||
<Compile Include="Widgets\HotkeyEntryWidget.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace OpenRA.Widgets
|
||||
{
|
||||
public class ButtonWidget : Widget
|
||||
{
|
||||
public Func<ButtonWidget, string> GetKey = _ => null;
|
||||
public string Key
|
||||
public Func<ButtonWidget, Hotkey> 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 (KeycodeExts.DisplayString(e.Key) != Key || e.Event != KeyInputEvent.Down)
|
||||
if (Hotkey.FromKeyInput(e) != Key || e.Event != KeyInputEvent.Down)
|
||||
return false;
|
||||
|
||||
if (!IsDisabled())
|
||||
|
||||
122
OpenRA.Game/Widgets/HotkeyEntryWidget.cs
Normal file
122
OpenRA.Game/Widgets/HotkeyEntryWidget.cs
Normal file
@@ -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<bool> 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); }
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace OpenRA.Widgets
|
||||
}
|
||||
|
||||
// Disable pausing for spectators
|
||||
else if (KeycodeExts.DisplayString(e.Key) == 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -274,8 +274,10 @@ namespace OpenRA.Mods.Cnc.Widgets
|
||||
|
||||
public override bool HandleKeyPress(KeyInput e)
|
||||
{
|
||||
if (e.Event != KeyInputEvent.Down) return false;
|
||||
if (KeycodeExts.DisplayString(e.Key) == 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));
|
||||
|
||||
@@ -146,8 +146,10 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
|
||||
public override bool HandleKeyPress(KeyInput e)
|
||||
{
|
||||
if (e.Event == KeyInputEvent.Up) return false;
|
||||
if (KeycodeExts.DisplayString(e.Key) == 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;
|
||||
|
||||
@@ -335,22 +335,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetupKeyBinding(ScrollItemWidget keyWidget, string description, Func<string> getValue, Action<string> setValue)
|
||||
void SetupKeyBinding(ScrollItemWidget keyWidget, string description, Func<Hotkey> getValue, Action<Hotkey> setValue)
|
||||
{
|
||||
keyWidget.Get<LabelWidget>("FUNCTION").GetText = () => description;
|
||||
|
||||
var textBox = keyWidget.Get<TextFieldWidget>("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<HotkeyEntryWidget>("HOTKEY");
|
||||
keyEntry.Key = getValue();
|
||||
keyEntry.OnLoseFocus = () => setValue(keyEntry.Key);
|
||||
}
|
||||
|
||||
static bool ShowRendererDropdown(DropDownButtonWidget dropdown, GraphicSettings s)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,38 +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)
|
||||
{
|
||||
var key = Hotkey.FromKeyInput(e);
|
||||
var ks = Game.Settings.Keys;
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.CycleBaseKey)
|
||||
if (key == ks.CycleBaseKey)
|
||||
return CycleBases();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.ToLastEventKey)
|
||||
if (key == ks.ToLastEventKey)
|
||||
return ToLastEvent();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.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 (KeycodeExts.DisplayString(e.Key) == ks.AttackMoveKey)
|
||||
if (key == ks.AttackMoveKey)
|
||||
return PerformAttackMove();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.StopKey)
|
||||
if (key == ks.StopKey)
|
||||
return PerformStop();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.ScatterKey)
|
||||
if (key == ks.ScatterKey)
|
||||
return PerformScatter();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.DeployKey)
|
||||
if (key == ks.DeployKey)
|
||||
return PerformDeploy();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.StanceCycleKey)
|
||||
if (key == ks.StanceCycleKey)
|
||||
return PerformStanceCycle();
|
||||
|
||||
if (KeycodeExts.DisplayString(e.Key) == ks.GuardKey)
|
||||
if (key == ks.GuardKey)
|
||||
return PerformGuard();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user