#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * 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 System.Collections.Generic; using System.Linq; using OpenRA.Primitives; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic { public class HotkeysSettingsLogic : ChromeLogic { [TranslationReference("key")] const string OriginalNotice = "label-original-notice"; [TranslationReference("key", "context")] const string DuplicateNotice = "label-duplicate-notice"; readonly ModData modData; readonly Dictionary logicArgs; ScrollPanelWidget hotkeyList; ButtonWidget selectedHotkeyButton; HotkeyEntryWidget hotkeyEntryWidget; HotkeyDefinition duplicateHotkeyDefinition, selectedHotkeyDefinition; int validHotkeyEntryWidth; int invalidHotkeyEntryWidth; bool isHotkeyValid; bool isHotkeyDefault; string currentContext = "Any"; readonly HashSet contexts = new() { "Any" }; readonly Dictionary> hotkeyGroups = new(); TextFieldWidget filterInput; Widget headerTemplate; Widget template; Widget emptyListMessage; Widget remapDialog; static HotkeysSettingsLogic() { } [ObjectCreator.UseCtor] public HotkeysSettingsLogic( Action>, Func> registerPanel, string panelID, string label, ModData modData, Dictionary logicArgs) { this.modData = modData; this.logicArgs = logicArgs; registerPanel(panelID, label, InitPanel, ResetPanel); } void BindHotkeyPref(HotkeyDefinition hd, Widget template) { var key = template.Clone(); key.Id = hd.Name; key.IsVisible = () => true; key.Get("FUNCTION").GetText = () => hd.Description + ":"; var remapButton = key.Get("HOTKEY"); WidgetUtils.TruncateButtonToTooltip(remapButton, modData.Hotkeys[hd.Name].GetValue().DisplayString()); remapButton.IsHighlighted = () => selectedHotkeyDefinition == hd; var hotkeyValidColor = ChromeMetrics.Get("HotkeyColor"); var hotkeyInvalidColor = ChromeMetrics.Get("HotkeyColorInvalid"); remapButton.GetColor = () => hd.HasDuplicates ? hotkeyInvalidColor : hotkeyValidColor; if (selectedHotkeyDefinition == hd) { selectedHotkeyButton = remapButton; hotkeyEntryWidget.Key = modData.Hotkeys[hd.Name].GetValue(); ValidateHotkey(); } remapButton.OnClick = () => { selectedHotkeyDefinition = hd; selectedHotkeyButton = remapButton; hotkeyEntryWidget.Key = modData.Hotkeys[hd.Name].GetValue(); ValidateHotkey(); if (hd.Readonly) hotkeyEntryWidget.YieldKeyboardFocus(); else hotkeyEntryWidget.TakeKeyboardFocus(); }; hotkeyList.AddChild(key); } Func InitPanel(Widget panel) { hotkeyList = panel.Get("HOTKEY_LIST"); hotkeyList.Layout = new GridLayout(hotkeyList); headerTemplate = hotkeyList.Get("HEADER"); template = hotkeyList.Get("TEMPLATE"); emptyListMessage = panel.Get("HOTKEY_EMPTY_LIST"); remapDialog = panel.Get("HOTKEY_REMAP_DIALOG"); foreach (var hd in modData.Hotkeys.Definitions) contexts.UnionWith(hd.Contexts); filterInput = panel.Get("FILTER_INPUT"); filterInput.OnTextEdited = () => InitHotkeyList(); filterInput.OnEscKey = _ => { if (string.IsNullOrEmpty(filterInput.Text)) filterInput.YieldKeyboardFocus(); else { filterInput.Text = ""; filterInput.OnTextEdited(); } return true; }; var contextDropdown = panel.GetOrNull("CONTEXT_DROPDOWN"); if (contextDropdown != null) { contextDropdown.OnMouseDown = _ => ShowContextDropdown(contextDropdown); var contextName = new CachedTransform(GetContextDisplayName); contextDropdown.GetText = () => contextName.Update(currentContext); } if (logicArgs.TryGetValue("HotkeyGroups", out var hotkeyGroupsYaml)) { foreach (var hg in hotkeyGroupsYaml.Nodes) { var typesNode = hg.Value.NodeWithKeyOrDefault("Types"); if (typesNode != null) hotkeyGroups.Add(hg.Key, FieldLoader.GetValue>("Types", typesNode.Value.Value)); } InitHotkeyRemapDialog(panel); InitHotkeyList(); } return () => { hotkeyEntryWidget.Key = selectedHotkeyDefinition != null ? modData.Hotkeys[selectedHotkeyDefinition.Name].GetValue() : Hotkey.Invalid; hotkeyEntryWidget.ForceYieldKeyboardFocus(); return false; }; } Action ResetPanel(Widget panel) { return () => { foreach (var hd in modData.Hotkeys.Definitions) { modData.Hotkeys.Set(hd.Name, hd.Default); var hotkeyButton = panel.GetOrNull(hd.Name)?.Get("HOTKEY"); if (hotkeyButton != null) WidgetUtils.TruncateButtonToTooltip(hotkeyButton, hd.Default.DisplayString()); } }; } void InitHotkeyList() { hotkeyList.RemoveChildren(); selectedHotkeyDefinition = null; foreach (var hg in hotkeyGroups) { var typesInGroup = hg.Value; var keysInGroup = modData.Hotkeys.Definitions .Where(hd => IsHotkeyVisibleInFilter(hd) && hd.Types.Overlaps(typesInGroup)) .ToList(); if (keysInGroup.Count == 0) continue; var header = headerTemplate.Clone(); header.Get("LABEL").GetText = () => hg.Key; hotkeyList.AddChild(header); var added = new HashSet(); foreach (var type in typesInGroup) { foreach (var hd in keysInGroup.Where(k => k.Types.Contains(type))) { if (added.Add(hd)) { selectedHotkeyDefinition ??= hd; BindHotkeyPref(hd, template); } } } } emptyListMessage.Visible = selectedHotkeyDefinition == null; remapDialog.Visible = selectedHotkeyDefinition != null; hotkeyList.ScrollToTop(); } void InitHotkeyRemapDialog(Widget panel) { var label = panel.Get("HOTKEY_LABEL"); var labelText = new CachedTransform(hd => hd?.Description + ":"); label.IsVisible = () => selectedHotkeyDefinition != null; label.GetText = () => labelText.Update(selectedHotkeyDefinition); var duplicateNotice = panel.Get("DUPLICATE_NOTICE"); duplicateNotice.TextColor = ChromeMetrics.Get("NoticeErrorColor"); duplicateNotice.IsVisible = () => !isHotkeyValid; var duplicateNoticeText = new CachedTransform(hd => hd != null ? TranslationProvider.GetString(DuplicateNotice, Translation.Arguments("key", hd.Description, "context", hd.Contexts.First(c => selectedHotkeyDefinition.Contexts.Contains(c)))) : ""); duplicateNotice.GetText = () => duplicateNoticeText.Update(duplicateHotkeyDefinition); var originalNotice = panel.Get("ORIGINAL_NOTICE"); originalNotice.TextColor = ChromeMetrics.Get("NoticeInfoColor"); originalNotice.IsVisible = () => isHotkeyValid && !isHotkeyDefault; var originalNoticeText = new CachedTransform(hd => TranslationProvider.GetString(OriginalNotice, Translation.Arguments("key", hd?.Default.DisplayString()))); originalNotice.GetText = () => originalNoticeText.Update(selectedHotkeyDefinition); var readonlyNotice = panel.Get("READONLY_NOTICE"); readonlyNotice.TextColor = ChromeMetrics.Get("NoticeInfoColor"); readonlyNotice.IsVisible = () => selectedHotkeyDefinition.Readonly; var resetButton = panel.Get("RESET_HOTKEY_BUTTON"); resetButton.IsDisabled = () => isHotkeyDefault || selectedHotkeyDefinition.Readonly; resetButton.OnClick = ResetHotkey; var clearButton = panel.Get("CLEAR_HOTKEY_BUTTON"); clearButton.IsDisabled = () => selectedHotkeyDefinition.Readonly || !hotkeyEntryWidget.Key.IsValid(); clearButton.OnClick = ClearHotkey; var overrideButton = panel.Get("OVERRIDE_HOTKEY_BUTTON"); overrideButton.IsDisabled = () => isHotkeyValid; overrideButton.IsVisible = () => !isHotkeyValid && !duplicateHotkeyDefinition.Readonly; overrideButton.OnClick = OverrideHotkey; hotkeyEntryWidget = panel.Get("HOTKEY_ENTRY"); hotkeyEntryWidget.IsValid = () => isHotkeyValid; hotkeyEntryWidget.OnLoseFocus = ValidateHotkey; hotkeyEntryWidget.OnEscKey = _ => hotkeyEntryWidget.Key = modData.Hotkeys[selectedHotkeyDefinition.Name].GetValue(); hotkeyEntryWidget.IsDisabled = () => selectedHotkeyDefinition.Readonly; validHotkeyEntryWidth = hotkeyEntryWidget.Bounds.Width; invalidHotkeyEntryWidth = validHotkeyEntryWidth - (clearButton.Bounds.X - overrideButton.Bounds.X); } void ValidateHotkey() { if (selectedHotkeyDefinition == null) return; duplicateHotkeyDefinition = modData.Hotkeys.GetFirstDuplicate(selectedHotkeyDefinition, hotkeyEntryWidget.Key); isHotkeyValid = duplicateHotkeyDefinition == null || selectedHotkeyDefinition.Readonly; isHotkeyDefault = hotkeyEntryWidget.Key == selectedHotkeyDefinition.Default || (!hotkeyEntryWidget.Key.IsValid() && !selectedHotkeyDefinition.Default.IsValid()); if (isHotkeyValid) { hotkeyEntryWidget.Bounds.Width = validHotkeyEntryWidth; SaveHotkey(); } else { hotkeyEntryWidget.Bounds.Width = duplicateHotkeyDefinition.Readonly ? validHotkeyEntryWidth : invalidHotkeyEntryWidth; hotkeyEntryWidget.TakeKeyboardFocus(); } } void SaveHotkey() { if (selectedHotkeyDefinition.Readonly) return; WidgetUtils.TruncateButtonToTooltip(selectedHotkeyButton, hotkeyEntryWidget.Key.DisplayString()); modData.Hotkeys.Set(selectedHotkeyDefinition.Name, hotkeyEntryWidget.Key); Game.Settings.Save(); } void ResetHotkey() { hotkeyEntryWidget.Key = selectedHotkeyDefinition.Default; hotkeyEntryWidget.YieldKeyboardFocus(); } void ClearHotkey() { hotkeyEntryWidget.Key = Hotkey.Invalid; hotkeyEntryWidget.YieldKeyboardFocus(); } void OverrideHotkey() { var duplicateHotkeyButton = hotkeyList.GetOrNull(duplicateHotkeyDefinition.Name)?.Get("HOTKEY"); if (duplicateHotkeyButton != null) WidgetUtils.TruncateButtonToTooltip(duplicateHotkeyButton, Hotkey.Invalid.DisplayString()); modData.Hotkeys.Set(duplicateHotkeyDefinition.Name, Hotkey.Invalid); Game.Settings.Save(); hotkeyEntryWidget.YieldKeyboardFocus(); } bool IsHotkeyVisibleInFilter(HotkeyDefinition hd) { var filter = filterInput.Text; var isFilteredByName = string.IsNullOrWhiteSpace(filter) || hd.Description.Contains(filter, StringComparison.CurrentCultureIgnoreCase); var isFilteredByContext = currentContext == "Any" || hd.Contexts.Contains(currentContext); return isFilteredByName && isFilteredByContext; } bool ShowContextDropdown(DropDownButtonWidget dropdown) { hotkeyEntryWidget.YieldKeyboardFocus(); var contextName = new CachedTransform(GetContextDisplayName); ScrollItemWidget SetupItem(string context, ScrollItemWidget itemTemplate) { var item = ScrollItemWidget.Setup(itemTemplate, () => currentContext == context, () => { currentContext = context; InitHotkeyList(); }); item.Get("LABEL").GetText = () => contextName.Update(context); return item; } dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 280, contexts, SetupItem); return true; } static string GetContextDisplayName(string context) { if (string.IsNullOrEmpty(context)) return "Any"; return context; } } }