Skip to content

Commit

Permalink
Add hotkey settings panel with a hotkey remap dialog
Browse files Browse the repository at this point in the history
* Add HotkeyDialogLogic.cs
* Add dialog-hotkey.yaml to all mods
* Add `GetFirstDuplicate` method to HotkeyManager to aid in validation
* Add "Player" and/or "Spectator" type to all hotkeys to allow for
validation based on overlapping types
* Change settings.yaml and SettingsLogic.cs to work with the new dialog
  • Loading branch information
dragunoff committed Jun 23, 2019
1 parent 1ae1b54 commit 1589d93
Show file tree
Hide file tree
Showing 23 changed files with 619 additions and 156 deletions.
14 changes: 14 additions & 0 deletions OpenRA.Game/HotkeyManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ public void Set(string name, Hotkey value)
settings.Remove(name);
}

public HotkeyDefinition GetFirstDuplicate(string name, Hotkey value, HotkeyDefinition definition)
{
foreach (var kv in keys)
{
if (kv.Key == name)
continue;

if (kv.Value == value && definitions[kv.Key].Types.Overlaps(definition.Types))
return definitions[kv.Key];
}

return null;
}

public HotkeyReference this[string name]
{
get
Expand Down
27 changes: 25 additions & 2 deletions OpenRA.Mods.Common/Widgets/HotkeyEntryWidget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ public class HotkeyEntryWidget : Widget
public int LeftMargin = 5;
public int RightMargin = 5;

public Action OnTakeFocus = () => { };
public Action OnLoseFocus = () => { };
public Action OnEscape = () => { };
public Action OnReturn = () => { };

public Func<bool> IsDisabled = () => false;
public Func<bool> IsValid = () => false;
public string Font = ChromeMetrics.Get<string>("HotkeyFont");
public Color TextColor = ChromeMetrics.Get<Color>("HotkeyColor");
public Color TextColorDisabled = ChromeMetrics.Get<Color>("HotkeyColorDisabled");
public Color TextColorInvalid = ChromeMetrics.Get<Color>("HotkeyColorInvalid");

public HotkeyEntryWidget() { }
protected HotkeyEntryWidget(HotkeyEntryWidget widget)
Expand All @@ -38,12 +43,22 @@ protected HotkeyEntryWidget(HotkeyEntryWidget widget)
Font = widget.Font;
TextColor = widget.TextColor;
TextColorDisabled = widget.TextColorDisabled;
TextColorInvalid = widget.TextColorInvalid;
VisualHeight = widget.VisualHeight;
}

public override bool TakeKeyboardFocus()
{
OnTakeFocus();
return base.TakeKeyboardFocus();
}

public override bool YieldKeyboardFocus()
{
OnLoseFocus();
if (!IsValid())
return false;

return base.YieldKeyboardFocus();
}

Expand Down Expand Up @@ -80,7 +95,14 @@ public override bool HandleKeyPress(KeyInput e)
if (!HasKeyboardFocus || IgnoreKeys.Contains(e.Key))
return false;

Key = Hotkey.FromKeyInput(e);
if (e.Key != Keycode.ESCAPE && e.Key != Keycode.RETURN)
Key = Hotkey.FromKeyInput(e);

if (e.Key == Keycode.ESCAPE)
OnEscape();

if (e.Key == Keycode.RETURN)
OnReturn();

YieldKeyboardFocus();

Expand Down Expand Up @@ -109,6 +131,7 @@ public override void Draw()
var textSize = font.Measure(apparentText);

var disabled = IsDisabled();
var valid = IsValid();
var state = disabled ? "textfield-disabled" :
HasKeyboardFocus ? "textfield-focused" :
Ui.MouseOverWidget == this ? "textfield-hover" :
Expand All @@ -131,7 +154,7 @@ public override void Draw()
Bounds.Width - LeftMargin - RightMargin, Bounds.Bottom));
}

var color = disabled ? TextColorDisabled : TextColor;
var color = disabled ? TextColorDisabled : !valid ? TextColorInvalid : TextColor;
font.DrawText(apparentText, textPos, color);

if (isTextOverflowing)
Expand Down
149 changes: 149 additions & 0 deletions OpenRA.Mods.Common/Widgets/Logic/HotkeyDialogLogic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#region Copyright & License Information
/*
* Copyright 2007-2019 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using System;
using OpenRA.Primitives;
using OpenRA.Widgets;

namespace OpenRA.Mods.Common.Widgets.Logic
{
public class HotkeyDialogLogic : ChromeLogic
{
readonly Widget panel;
readonly ButtonWidget resetButton, clearButton, cancelButton;
readonly LabelWidget duplicateNotice, defaultNotice, originalNotice;
readonly Action onSave;
readonly HotkeyDefinition definition;
readonly HotkeyManager manager;
readonly HotkeyEntryWidget hotkeyEntry;
readonly bool isFirstValidation = true;
Hotkey currentHotkey;
HotkeyDefinition duplicateHotkey;
bool isValid = false;
bool isValidating = false;

[ObjectCreator.UseCtor]
public HotkeyDialogLogic(Widget widget, Action onSave, HotkeyDefinition hotkeyDefinition, HotkeyManager hotkeyManager)
{
panel = widget;
this.onSave = onSave;
definition = hotkeyDefinition;
manager = hotkeyManager;
currentHotkey = manager[definition.Name].GetValue();
hotkeyEntry = panel.Get<HotkeyEntryWidget>("HOTKEY_ENTRY");
resetButton = panel.Get<ButtonWidget>("RESET_BUTTON");
clearButton = panel.Get<ButtonWidget>("CLEAR_BUTTON");
cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
duplicateNotice = panel.Get<LabelWidget>("DUPLICATE_NOTICE");
defaultNotice = panel.Get<LabelWidget>("DEFAULT_NOTICE");
originalNotice = panel.Get<LabelWidget>("ORIGINAL_NOTICE");

panel.Get<LabelWidget>("HOTKEY_LABEL").GetText = () => hotkeyDefinition.Description + ":";

duplicateNotice.TextColor = ChromeMetrics.Get<Color>("NoticeErrorColor");
duplicateNotice.GetText = () =>
{
return (duplicateHotkey != null) ? duplicateNotice.Text.F(duplicateHotkey.Description) : duplicateNotice.Text;
};
defaultNotice.TextColor = ChromeMetrics.Get<Color>("NoticeInfoColor");
originalNotice.TextColor = ChromeMetrics.Get<Color>("NoticeInfoColor");
originalNotice.Text = originalNotice.Text.F(hotkeyDefinition.Default.DisplayString());

resetButton.OnClick = Reset;
clearButton.OnClick = Clear;
cancelButton.OnClick = Cancel;

hotkeyEntry.Key = currentHotkey;
hotkeyEntry.IsValid = () => isValid;
hotkeyEntry.OnTakeFocus = OnHotkeyEntryTakeFocus;
hotkeyEntry.OnLoseFocus = OnHotkeyEntryLoseFocus;
hotkeyEntry.OnEscape = Cancel;
hotkeyEntry.OnReturn = Cancel;
hotkeyEntry.TakeKeyboardFocus();

Validate();
isFirstValidation = false;
}

void OnHotkeyEntryTakeFocus()
{
cancelButton.Disabled = manager.GetFirstDuplicate(definition.Name, currentHotkey, definition) != null;
}

void OnHotkeyEntryLoseFocus()
{
cancelButton.Disabled = true;
if (!isValidating)
Validate();
}

void Validate()
{
isValidating = true;

duplicateHotkey = manager.GetFirstDuplicate(definition.Name, hotkeyEntry.Key, definition);
isValid = duplicateHotkey == null;

duplicateNotice.Visible = !isValid;
clearButton.Disabled = !hotkeyEntry.Key.IsValid();

if (hotkeyEntry.Key == definition.Default || (!hotkeyEntry.Key.IsValid() && !definition.Default.IsValid()))
{
defaultNotice.Visible = !duplicateNotice.Visible;
originalNotice.Visible = false;
resetButton.Disabled = true;
}
else
{
defaultNotice.Visible = false;
originalNotice.Visible = !duplicateNotice.Visible;
resetButton.Disabled = false;
}

if (isValid && !isFirstValidation)
{
currentHotkey = hotkeyEntry.Key;
hotkeyEntry.YieldKeyboardFocus();
Save();
}
else
hotkeyEntry.TakeKeyboardFocus();

isValidating = false;
}

void Save()
{
manager.Set(definition.Name, hotkeyEntry.Key);
Game.Settings.Save();
onSave();
}

void Cancel()
{
cancelButton.Disabled = true;
hotkeyEntry.Key = currentHotkey;
Validate();
}

void Reset()
{
hotkeyEntry.Key = definition.Default;
Validate();
}

void Clear()
{
hotkeyEntry.Key = Hotkey.Invalid;
Validate();
}
}
}
81 changes: 70 additions & 11 deletions OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Widgets;

namespace OpenRA.Mods.Common.Widgets.Logic
{
public class SettingsLogic : ChromeLogic
{
enum PanelType { Display, Audio, Input, Advanced }
enum PanelType { Display, Audio, Input, Hotkeys, Advanced }

static readonly string OriginalSoundDevice;
static readonly WindowMode OriginalGraphicsMode;
Expand Down Expand Up @@ -61,6 +62,7 @@ public SettingsLogic(Widget widget, Action onExit, ModData modData, WorldRendere
RegisterSettingsPanel(PanelType.Display, InitDisplayPanel, ResetDisplayPanel, "DISPLAY_PANEL", "DISPLAY_TAB");
RegisterSettingsPanel(PanelType.Audio, InitAudioPanel, ResetAudioPanel, "AUDIO_PANEL", "AUDIO_TAB");
RegisterSettingsPanel(PanelType.Input, InitInputPanel, ResetInputPanel, "INPUT_PANEL", "INPUT_TAB");
RegisterSettingsPanel(PanelType.Hotkeys, InitHotkeysPanel, ResetHotkeysPanel, "HOTKEYS_PANEL", "HOTKEYS_TAB");
RegisterSettingsPanel(PanelType.Advanced, InitAdvancedPanel, ResetAdvancedPanel, "ADVANCED_PANEL", "ADVANCED_TAB");

panelContainer.Get<ButtonWidget>("BACK_BUTTON").OnClick = () =>
Expand Down Expand Up @@ -123,17 +125,61 @@ static void BindSliderPref(Widget parent, string id, object group, string pref)
ss.OnChange += x => field.SetValue(group, x);
}

static void BindHotkeyPref(HotkeyDefinition hd, HotkeyManager manager, Widget template, Widget parent)
static void BindHotkeyPref(HotkeyDefinition hd, HotkeyManager manager, Widget template, Widget parent, Widget remapDialogRoot, Widget remapDialogPlaceholder)
{
var key = template.Clone() as Widget;
key.Id = hd.Name;
key.IsVisible = () => true;

key.Get<LabelWidget>("FUNCTION").GetText = () => hd.Description + ":";

var textBox = key.Get<HotkeyEntryWidget>("HOTKEY");
textBox.Key = manager[hd.Name].GetValue();
textBox.OnLoseFocus = () => manager.Set(hd.Name, textBox.Key);
var remapButton = key.Get<ButtonWidget>("HOTKEY");
remapButton.GetText = () => manager[hd.Name].GetValue().DisplayString();

if (manager.GetFirstDuplicate(hd.Name, manager[hd.Name].GetValue(), hd) != null)
remapButton.GetColor = () => ChromeMetrics.Get<Color>("HotkeyColorInvalid");

remapButton.OnClick = () =>
{
remapDialogRoot.RemoveChildren();
if (remapButton.IsHighlighted())
{
remapButton.IsHighlighted = () => false;
if (remapDialogPlaceholder != null)
remapDialogPlaceholder.Visible = true;
return;
}
if (remapDialogPlaceholder != null)
remapDialogPlaceholder.Visible = false;
var siblings = parent.Children;
foreach (var sibling in siblings)
{
var button = sibling.GetOrNull<ButtonWidget>("HOTKEY");
if (button != null)
button.IsHighlighted = () => false;
}
remapButton.IsHighlighted = () => true;
Ui.LoadWidget("HOTKEY_DIALOG", remapDialogRoot, new WidgetArgs
{
{
"onSave", () =>
{
remapButton.GetText = () => manager[hd.Name].GetValue().DisplayString();
remapButton.GetColor = () => ChromeMetrics.Get<Color>("ButtonTextColor");
}
},
{ "hotkeyDefinition", hd },
{ "hotkeyManager", manager },
});
};

parent.AddChild(key);
}

Expand Down Expand Up @@ -408,6 +454,11 @@ Action InitInputPanel(Widget panel)
zoomModifierDropdown.OnMouseDown = _ => ShowZoomModifierDropdown(zoomModifierDropdown, gs);
zoomModifierDropdown.GetText = () => gs.ZoomModifier.ToString();

return () => { };
}

Action InitHotkeysPanel(Widget panel)
{
var hotkeyList = panel.Get<ScrollPanelWidget>("HOTKEY_LIST");
hotkeyList.Layout = new GridLayout(hotkeyList);
var hotkeyHeader = hotkeyList.Get<ScrollItemWidget>("HEADER");
Expand All @@ -434,10 +485,12 @@ Action InitInputPanel(Widget panel)
var types = FieldLoader.GetValue<string[]>("Types", typesNode.Value.Value);
var added = new HashSet<HotkeyDefinition>();
var template = templates.Get(templateNode.Value.Value);
var remapDialogRoot = panel.Get("HOTKEY_DIALOG_ROOT");
var remapDialogPlaceholder = panel.GetOrNull("HOTKEY_DIALOG_PLACEHOLDER");
foreach (var t in types)
foreach (var hd in modData.Hotkeys.Definitions.Where(k => k.Types.Contains(t)))
if (added.Add(hd))
BindHotkeyPref(hd, modData.Hotkeys, template, hotkeyList);
BindHotkeyPref(hd, modData.Hotkeys, template, hotkeyList, remapDialogRoot, remapDialogPlaceholder);
}
}

Expand All @@ -461,16 +514,22 @@ Action ResetInputPanel(Widget panel)
gs.AllowZoom = dgs.AllowZoom;
gs.ZoomModifier = dgs.ZoomModifier;
panel.Get<SliderWidget>("SCROLLSPEED_SLIDER").Value = gs.ViewportEdgeScrollStep;
panel.Get<SliderWidget>("UI_SCROLLSPEED_SLIDER").Value = gs.UIScrollSpeed;
MakeMouseFocusSettingsLive();
};
}

Action ResetHotkeysPanel(Widget panel)
{
return () =>
{
foreach (var hd in modData.Hotkeys.Definitions)
{
modData.Hotkeys.Set(hd.Name, hd.Default);
panel.Get(hd.Name).Get<HotkeyEntryWidget>("HOTKEY").Key = hd.Default;
}
panel.Get<SliderWidget>("SCROLLSPEED_SLIDER").Value = gs.ViewportEdgeScrollStep;
panel.Get<SliderWidget>("UI_SCROLLSPEED_SLIDER").Value = gs.UIScrollSpeed;
MakeMouseFocusSettingsLive();
};
}

Expand Down
Loading

0 comments on commit 1589d93

Please sign in to comment.