Index: binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/hotkeys/HotkeyPicker.js @@ -0,0 +1,69 @@ +/** + * Handle the interface to pick a hotkey combination. + * The player must keep a key combination for 2s in the input field for it to be registered. + */ +class HotkeyPicker +{ + constructor(onClose, name, mapping) + { + this.name = name; + + this.window = Engine.GetGUIObjectByName("hotkeyPicker"); + this.window.hidden = false; + + Engine.GetGUIObjectByName("hotkeyPickerDesc").caption = '' + + `Entering hotkey for ${name}.\nCurrent hotkey mapping is ${mapping}`; + + this.text = Engine.GetGUIObjectByName("hotkeyPickerText"); + this.text.caption = translate("Start pressing keys to pick a hotkey."); + this.input = Engine.GetGUIObjectByName("hotkeyPickerInput"); + this.input.focus(); + + this.combination = null; + + this.input.onKeyChange = keys => this.text.caption = keys.join("+") + translate("\n(Hold for one second to register)"); + this.input.onCombination = keys => { + this.text.caption = translate(`Mapping: ${keys.join("+")}`); + this.input.blur(); + this.combination = keys; + + let conflicts = (Engine.GetConflicts(this.combination) || []) + .map(translate).filter(name => name != this.name); + if (conflicts.length) + Engine.GetGUIObjectByName("hotkeyPickerConflicts").caption = + translate(`${coloredText("Conflicts with", "255 153 0")}: ${conflicts.join(", ")}`); + + this.renderButtons(); + }; + + this.renderButtons(); + + Engine.GetGUIObjectByName("hotkeyPickerCancel").onPress = () => { + this.combination = null; + onClose(this); + }; + Engine.GetGUIObjectByName("hotkeyPickerAccept").onPress = () => onClose(this); + Engine.GetGUIObjectByName("hotkeyPickerRepick").onPress = () => { + this.input.focus(); + this.combination = null; + this.text.caption = translate("Start pressing keys to pick a hotkey."); + Engine.GetGUIObjectByName("hotkeyPickerConflicts").caption = ""; + this.renderButtons(); + }; + + this.combination = null; + this.input.focus(); + } + + close() + { + this.window.hidden = true; + this.input.blur(); + } + + renderButtons() + { + Engine.GetGUIObjectByName("hotkeyPickerAccept").enabled = !!this.combination; + Engine.GetGUIObjectByName("hotkeyPickerRepick").enabled = !!this.combination; + } +} Index: binaries/data/mods/public/gui/hotkeys/HotkeysPage.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/hotkeys/HotkeysPage.js @@ -0,0 +1,186 @@ +class HotkeysPage +{ + constructor() + { + Engine.GetGUIObjectByName("hotkeyList").onMouseLeftDoubleClickItem = () => { + let idx = Engine.GetGUIObjectByName("hotkeyList").selected; + new HotkeyPicker( + this.onHotkeyPicked.bind(this), + Engine.GetGUIObjectByName("hotkeyList").list_name[idx], + Engine.GetGUIObjectByName("hotkeyList").list_mapping[idx] + ); + }; + Engine.GetGUIObjectByName("hotkeyFilter").onSelectionChange = () => this.setupHotkeyList(); + + Engine.GetGUIObjectByName("hotkeyTextFilter").onTextEdit = () => this.setupHotkeyList(); + + Engine.GetGUIObjectByName("hotkeyCancel").onPress = () => Engine.PopGuiPage(); + Engine.GetGUIObjectByName("hotkeyReset").onPress = () => this.resetUserHotkeys(); + Engine.GetGUIObjectByName("hotkeySave").onPress = () => { + this.saveUserHotkeys(); + }; + + this.categories = this.getHotkeyCategories(); + this.setupFilters(); + this.setupHotkeyList(); + } + + setupFilters() + { + let dropdown = Engine.GetGUIObjectByName("hotkeyFilter"); + let names = []; + for (let cat in this.categories) + names.push(this.categories[cat].label); + dropdown.list = [translate("All Hotkeys")].concat(names); + dropdown.list_data = [-1].concat(Object.keys(this.categories)); + dropdown.selected = 0; + } + + setupHotkeyList() + { + let hotkeyList = Engine.GetGUIObjectByName("hotkeyList"); + hotkeyList.selected = -1; + let textFilter = Engine.GetGUIObjectByName("hotkeyTextFilter").caption; + let dropdown = Engine.GetGUIObjectByName("hotkeyFilter"); + if (dropdown.selected && dropdown.selected !== 0) + { + let category = this.categories[dropdown.list_data[dropdown.selected]]; + // This is absurdly inefficient but it seems fast enough. + let hotkeys = category.hotkeys.filter(x => translate(x[0]).indexOf(textFilter) !== -1); + hotkeyList.list_name = hotkeys.map(x => translate(x[0])); + hotkeyList.list_mapping = hotkeys.map(x => this.formatHotkeyCombination(x[1])); + hotkeyList.list = hotkeys.map(() => 0); + hotkeyList.list_data = hotkeys.map(() => 0); + } + else + { + // TODO SM62+ : refactor using flat() + let flattened = []; + for (let cat in this.categories) + flattened = flattened.concat(this.categories[cat].hotkeys); + flattened = flattened.filter(x => translate(x[0]).indexOf(textFilter) !== -1); + hotkeyList.list_name = flattened.map(x => translate(x[0])); + hotkeyList.list_mapping = flattened.map(x => this.formatHotkeyCombination(x[1])); + hotkeyList.list = flattened.map(() => 0); + hotkeyList.list_data = flattened.map(() => 0); + } + } + + formatHotkeyCombination(comb) + { + let str = comb.sort(this.hotkeySort).join("+"); + return str.replace('\\', '\\\\').replace('[', '\\['); + } + + hotkeySort(a, b) + { + const specialKeys = ["Shift", "Alt", "Ctrl", "Super", "Super"]; + // Quick hack to put those first. + if (specialKeys.indexOf(a) !== -1) + a = ' ' + a; + if (specialKeys.indexOf(b) !== -1) + b = ' ' + b; + return a.localeCompare(b, Engine.GetCurrentLocale().substr(0,2), { "numeric": true }); + } + + onHotkeyPicked(picker) + { + picker.close(); + if (!picker.combination) + return; + let panel = Engine.GetGUIObjectByName("hotkeyList"); + for (let cat in this.categories) + { + let idx = this.categories[cat].hotkeys.findIndex(([name, mapping]) => name == picker.name); + if (idx === -1) + continue; + this.categories[cat].hotkeys[idx][1] = picker.combination; + } + + this.setupHotkeyList(); + } + + getHotkeyCategories() + { + let hotkeys = Engine.GetHotkeyMap(); + let categories = { + "other": { + "label": translate("Other hotkeys"), + "hotkeys": [] + } + }; + let n_categories = 1; + for (let hotkeyName in hotkeys) + { + let category = "other"; + let firstdot = hotkeyName.indexOf('.'); + if (firstdot !== -1) + category = hotkeyName.substr(0, firstdot); + if (!(category in categories)) + { + if (n_categories > 18) + category = "other"; + categories[category] = { + "label": category, + "hotkeys": [] + }; + } + categories[category].hotkeys.push([hotkeyName, hotkeys[hotkeyName]]); + } + // Remove categories that are too small to deserve a tab. + for (let cat of Object.keys(categories)) + if (categories[cat].hotkeys.length < 6) + { + categories.other.hotkeys = categories.other.hotkeys.concat(categories[cat].hotkeys); + delete categories[cat]; + } + for (let cat in categories) + categories[cat].hotkeys = categories[cat].hotkeys.sort(); + + return categories; + } + + resetUserHotkeys() + { + messageBox( + 400, 200, + translate("Reset all hotkeys to default values?"), + translate("Confirmation"), + [translate("No"), translate("Yes")], + [ + () => {}, + () => { + for (let cat in this.categories) + this.categories[cat].hotkeys.forEach(([name, _]) => { + Engine.ConfigDB_RemoveValue("user", "hotkey." + name); + }); + Engine.ConfigDB_WriteFile("user", "config/user.cfg"); + Engine.ReloadHotkeys(); + this.categories = this.getHotkeyCategories(); + this.setupHotkeyList(); + } + ]); + } + + saveUserHotkeys() + { + for (let cat in this.categories) + { + this.categories[cat].hotkeys.forEach(([name, mapping]) => { + let keymap = mapping.sort(this.hotkeySort).join("+"); + Engine.ConfigDB_RemoveValue("user", "hotkey." + name); + let val = Engine.ConfigDB_GetValue("user", "hotkey." + name); + if (keymap !== val) + Engine.ConfigDB_CreateValue("user", "hotkey." + name, keymap); + }); + } + Engine.ConfigDB_WriteFile("user", "config/user.cfg"); + Engine.ReloadHotkeys(); + } +} + + +function init(data) +{ + let hotkeyPage = new HotkeysPage(data); +} Index: binaries/data/mods/public/gui/hotkeys/hotkeys.xml =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/hotkeys/hotkeys.xml @@ -0,0 +1,97 @@ + + + + +