Add hotkeys for opening GUI pages across the game
Some checks failed
checkrefs / lfscheck (push) Has been cancelled
checkrefs / checkrefs (push) Has been cancelled
lint / cppcheck (push) Has been cancelled
lint / copyright (push) Has been cancelled
lint / jenkinsfiles (push) Has been cancelled
pre-commit / build (push) Has been cancelled

Add registerGlobalGuiPageHotkeys() to
common/functions_utility.js to selectively register
GUI page hotkeys
Allow active GUI pages to close using their corresponding hotkey
Move page_hotkeys.xml from gui/hotkeys/ to gui/
Update page_hotkeys.xml references in MainMenuItems and MenuButtons
Add default tipScrolling fallback in TipsPage when no initData is provided
This commit is contained in:
guerringuerrin 2026-03-19 17:46:55 -03:00 committed by Vantha
parent 9df2dc2585
commit 32e5520507
23 changed files with 151 additions and 32 deletions

View file

@ -232,6 +232,12 @@ summary = "Ctrl+Tab" ; Toggle in-game summary
lobby = "Alt+L" ; Show the multiplayer lobby in a dialog window.
structree = "Alt+Shift+T" ; Show structure tree
civinfo = "Alt+Shift+H" ; Show civilization info
catafalque = "Alt+Shift+F" ; Show catafalque overview
mapbrowser = "Alt+Shift+M" ; Show map browser
manual = "Alt+Shift+U" ; Show manual
tips = "Alt+Shift+I" ; Show tips and tricks
options = "Alt+Shift+O" ; Show options
hotkeys = "Alt+Shift+Y" ; Show hotkeys
; > CLIPBOARD CONTROLS
copy = "Ctrl+C" ; Copy to clipboard
@ -388,9 +394,6 @@ defense_tower = "Space+W"
market = "Space+M"
dock = "Space+D"
[hotkey.gamesetup]
mapbrowser.open = "M"
[hotkey.session]
kill = Delete, Backspace ; Destroy selected units
stop = "H" ; Stop the current action

View file

@ -247,6 +247,7 @@ var g_CampaignMenu;
async function init(initData)
{
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "mapbrowser", "manual", "tips"]);
let run = initData?.filename || CampaignRun.getCurrentRunFilename();
try
{

View file

@ -143,5 +143,6 @@ var g_LoadModal;
function init()
{
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "mapbrowser", "manual", "tips"]);
return new Promise(closePageCallback => { g_LoadModal = new LoadModal(closePageCallback); });
}

View file

@ -45,6 +45,7 @@ function init(campaign_template_data)
{
return new Promise(closePageCallback =>
{
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "mapbrowser", "manual", "tips"]);
g_NewCampaignModal = new NewCampaignModal(campaign_template_data, closePageCallback);
});
}

View file

@ -75,6 +75,7 @@ var g_CampaignSetupPage;
function init()
{
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "manual", "tips", "mapbrowser"]);
return new Promise(closePageCallback =>
{
g_CampaignSetupPage = new CampaignSetupPage(closePageCallback);

View file

@ -325,4 +325,23 @@ function toPascalCase(str)
.split('_')
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
.join('');
}
/**
* Registers global hotkeys for opening GUI pages.
*
* Each hotkey opens a child page following the naming convention:
* page_${hotkey}.xml
*/
function registerGlobalGuiPageHotkeys(hotkeys)
{
for (const key of hotkeys)
{
const guiPage = `gui/page_${key}.xml`;
if (!Engine.FileExists(guiPage))
{
warn(`Skipping global hotkey '${key}': Missing GUI page '${guiPage}'.`);
continue;
}
Engine.SetGlobalHotkey(key, "Press", () => Engine.OpenChildPage(`page_${key}.xml`));
}
}

View file

@ -11,7 +11,7 @@ GameSettingControls.MapBrowser = class MapBrowser extends GameSettingControlButt
}
this.button.tooltip = colorizeHotkey(this.HotkeyTooltip, this.HotkeyConfig);
Engine.SetGlobalHotkey(this.HotkeyConfig, "Press", this.onPress.bind(this));
this.button.hotkey = this.HotkeyConfig;
}
onSettingsLoaded()
@ -31,15 +31,20 @@ GameSettingControls.MapBrowser = class MapBrowser extends GameSettingControlButt
onPress()
{
this.setupWindow.pages.MapBrowserPage.openPage(this.enabled);
const page = this.setupWindow.pages.MapBrowserPage;
if (!page.mapBrowserPage.hidden)
page.submitMapSelection();
else
page.openPage(this.enabled);
}
};
GameSettingControls.MapBrowser.prototype.HotkeyConfig =
"gamesetup.mapbrowser.open";
"mapbrowser";
GameSettingControls.MapBrowser.prototype.Caption =
translate("Browse Maps");
GameSettingControls.MapBrowser.prototype.HotkeyTooltip =
translate("Press %(hotkey)s to view the list of available maps.");
translate("%(hotkey)s: View the list of available maps.");

View file

@ -43,6 +43,7 @@ var g_SetupWindow;
function init(initData, hotloadData)
{
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "manual", "tips"]);
return Promise.race([new Promise(closePageCallback =>
{
g_SetupWindow = new SetupWindow(initData, hotloadData, closePageCallback);

View file

@ -193,6 +193,7 @@ function init()
// suppress the warning.
/* eslint-disable-next-line no-new */
new HotkeysPage(new HotkeyMetadata(), closePageCallback);
Engine.SetGlobalHotkey("hotkeys", "Press", closePageCallback);
});
}

View file

@ -106,6 +106,38 @@
"mousegrabtoggle": {
"name": "Toggle mouse grab",
"desc": "Constrain to window boundaries or release the mouse."
},
"structree": {
"name": "Open Structure Tree window",
"desc": "Open Structure Tree window."
},
"civinfo": {
"name": "Open Civilization Overview window",
"desc": "Open Civilization Overview window."
},
"catafalque": {
"name": "Open Catafalque Overview",
"desc": "Open Catafalque Overview window."
},
"mapbrowser": {
"name": "Open Map Browser window",
"desc": "Open Map Browser window."
},
"manual": {
"name": "Open the 0 A.D. Game Manual",
"desc": "Open the 0 A.D. Game Manual."
},
"tips": {
"name": "Open Tips and Tricks window",
"desc": "Open Tips and Tricks window."
},
"hotkeys": {
"name": "Open Hotkeys window",
"desc": "Open Hotkeys window."
},
"options": {
"name": "Open Options window",
"desc": "Open Options window."
}
}
}

View file

@ -39,14 +39,6 @@
"name": "Toggle MP lobby",
"desc": "Show the multiplayer lobby in a dialog window."
},
"structree": {
"name": "Toggle structure tree",
"desc": "Toggle the structure tree."
},
"civinfo": {
"name": "Toggle civ info",
"desc": "Toggle the civilization info."
},
"session.gui.menu.toggle": {
"name": "Toggle in-game menu",
"desc": "Toggle in-game menu."

View file

@ -36,6 +36,7 @@ async function init(attribs)
if (g_Settings)
return new Promise(closePageCallback =>
{
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "mapbrowser", "manual", "tips"]);
g_LobbyHandler = new LobbyHandler(closePageCallback, attribs && attribs.dialog);
});

View file

@ -11,5 +11,6 @@ function init()
return new Promise(closePageCallback =>
{
Engine.GetGUIObjectByName("closeButton").onPress = closePageCallback;
Engine.SetGlobalHotkey("manual", "Press", closePageCallback);
});
}
}

View file

@ -10,5 +10,9 @@ function init()
const browser = new MapBrowser(cache, filters);
browser.openPage(false);
browser.controls.MapFiltering.select("default", "skirmish");
return new Promise(closePageCallback => { browser.registerClosePageHandler(closePageCallback); });
}
Engine.SetGlobalHotkey("mapbrowser", "Press", () => browser.closePage());
return new Promise(closePageCallback =>
{
browser.registerClosePageHandler(closePageCallback);
});
}

View file

@ -234,7 +234,9 @@ async function init(data, hotloadData)
g_Options = Engine.ReadJSONFile("gui/options/options.json");
translateObjectKeys(g_Options, ["label", "tooltip"]);
deepfreeze(g_Options);
Engine.SetGlobalHotkey("options", "Press", () =>
Engine.GetGUIObjectByName("closeButton").onPress()
);
placeTabButtons(
g_Options,
false,

View file

@ -5,7 +5,8 @@ export const mainMenuItems = [
"submenu": [
{
"caption": translate("Manual"),
"tooltip": translate("Open the 0 A.D. Game Manual."),
"tooltip": colorizeHotkey(translate("%(hotkey)s: Open the 0 A.D. Game Manual."), "manual"),
"hotkey": "manual",
"onPress": () =>
{
Engine.OpenChildPage("page_manual.xml");
@ -41,7 +42,8 @@ export const mainMenuItems = [
},
{
"caption": translate("Tips and Tricks"),
"tooltip": translate("Discover simple tips, tricks, and game mechanics."),
"tooltip": colorizeHotkey(translate("%(hotkey)s: Discover simple tips, tricks, and game mechanics."), "tips"),
"hotkey": "tips",
"onPress": Engine.OpenChildPage.bind(null, "page_tips.xml", {
"tipScrolling": true
})
@ -66,7 +68,8 @@ export const mainMenuItems = [
},
{
"caption": translate("Catafalque Overview"),
"tooltip": translate("Compare the bonuses of catafalques featured in 0 A.D."),
"tooltip": colorizeHotkey(translate("%(hotkey)s: Compare the bonuses of catafalques featured in 0 A.D."), "catafalque"),
"hotkey": "catafalque",
"onPress": () =>
{
Engine.OpenChildPage("page_catafalque.xml");
@ -74,7 +77,8 @@ export const mainMenuItems = [
},
{
"caption": translate("Map Overview"),
"tooltip": translate("View the different maps featured in 0 A.D."),
"tooltip": colorizeHotkey(translate("%(hotkey)s: View the different maps featured in 0 A.D."), "mapbrowser"),
"hotkey": "mapbrowser",
"onPress": () =>
{
Engine.OpenChildPage("page_mapbrowser.xml");
@ -295,7 +299,8 @@ export const mainMenuItems = [
"submenu": [
{
"caption": translate("Options"),
"tooltip": translate("Adjust game settings."),
"tooltip": colorizeHotkey(translate("%(hotkey)s: Adjust game settings."), "options"),
"hotkey": "options",
"onPress": async() =>
{
fireConfigChangeHandlers(await Engine.OpenChildPage("page_options.xml"));
@ -303,10 +308,11 @@ export const mainMenuItems = [
},
{
"caption": translate("Hotkeys"),
"tooltip": translate("Adjust hotkeys."),
"tooltip": colorizeHotkey(translate("%(hotkey)s: Adjust hotkeys."), "hotkeys"),
"hotkey": "hotkeys",
"onPress": () =>
{
Engine.OpenChildPage("hotkeys/page_hotkeys.xml");
Engine.OpenChildPage("page_hotkeys.xml");
}
},
{

View file

@ -1,4 +1,8 @@
function init(data = {})
{
return new Promise(closePageCallback => { g_Page = new CatafalquePage(closePageCallback); });
return new Promise(closePageCallback =>
{
g_Page = new CatafalquePage(closePageCallback);
Engine.SetGlobalHotkey("catafalque", "Press", closePageCallback);
});
}

View file

@ -2,6 +2,8 @@ class TipsPage
{
constructor(initData, hotloadData, closePageCallback)
{
initData = { "tipScrolling": true, ...initData };
this.closePageCallback = closePageCallback;
this.tipDisplay = new TipDisplay(initData, hotloadData);
this.closeButton = new CloseButton(this);

View file

@ -5,6 +5,7 @@ function init(initData, hotloadData)
return new Promise(closePageCallback =>
{
g_TipsPage = new TipsPage(initData, hotloadData, closePageCallback);
Engine.SetGlobalHotkey("tips", "Press", closePageCallback);
});
}

View file

@ -77,7 +77,7 @@ async function init(data)
"page": "page_pregame.xml"
} };
}
registerGlobalGuiPageHotkeys(["options", "hotkeys", "civinfo", "structree", "catafalque", "mapbrowser", "manual", "tips"]);
initHotkeyTooltips();
displayReplayList();

View file

@ -13,7 +13,16 @@ MenuButtons.prototype.Manual = class
{
this.button = button;
this.button.caption = translate(translate("Manual"));
this.button.hotkey = "manual";
this.pauseControl = pauseControl;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Open the 0 A.D. game manual."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
@ -88,7 +97,7 @@ MenuButtons.prototype.Summary = class
rebuild()
{
this.button.tooltip = sprintf(translate("Press %(hotkey)s to open the summary screen."), {
this.button.tooltip = sprintf(translate("%(hotkey)s: Open the summary screen."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
@ -139,7 +148,7 @@ MenuButtons.prototype.Lobby = class
rebuild()
{
this.button.tooltip = sprintf(translate("Press %(hotkey)s to open the multiplayer lobby page without leaving the game."), {
this.button.tooltip = sprintf(translate("%(hotkey)s: Open the multiplayer lobby page without leaving the game."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
@ -159,7 +168,16 @@ MenuButtons.prototype.Options = class
{
this.button = button;
this.button.caption = translate("Options");
this.button.hotkey = "options";
this.pauseControl = pauseControl;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Adjust game settings."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
@ -178,7 +196,16 @@ MenuButtons.prototype.Hotkeys = class
{
this.button = button;
this.button.caption = translate("Hotkeys");
this.button.hotkey = "hotkeys";
this.pauseControl = pauseControl;
registerHotkeyChangeHandler(this.rebuild.bind(this));
}
rebuild()
{
this.button.tooltip = sprintf(translate("%(hotkey)s: Adjust hotkeys."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}
async onPress()
@ -186,7 +213,7 @@ MenuButtons.prototype.Hotkeys = class
closeOpenDialogs();
this.pauseControl.implicitPause();
await Engine.OpenChildPage("hotkeys/page_hotkeys.xml");
await Engine.OpenChildPage("page_hotkeys.xml");
resumeGame();
}
};
@ -211,7 +238,7 @@ MenuButtons.prototype.Pause = class
{
this.button.enabled = this.pauseControl.canPause(true);
this.button.caption = this.pauseControl.explicitPause ? translate("Resume") : translate("Pause");
this.button.tooltip = sprintf(translate("Press %(hotkey)s to pause or resume the game."), {
this.button.tooltip = sprintf(translate("%(hotkey)s: Pause or resume the game."), {
"hotkey": colorizeHotkey("%(hotkey)s", this.button.hotkey),
});
}

View file

@ -316,6 +316,20 @@ async function init(initData, hotloadData)
updatePlayerData();
initializeMusic(); // before changing the perspective
Engine.SetBoundingBoxDebugOverlay(false);
Engine.SetGlobalHotkey("catafalque", "Press", async() =>
{
closeOpenDialogs();
g_PauseControl.implicitPause();
await Engine.OpenChildPage("page_catafalque.xml");
resumeGame();
});
Engine.SetGlobalHotkey("tips", "Press", async() =>
{
closeOpenDialogs();
g_PauseControl.implicitPause();
await Engine.OpenChildPage("page_tips.xml");
resumeGame();
});
const promise = Promise.race([g_IsNetworked ? handleNetMessages() : new Promise(() => {}),
new Promise(closePageCallback =>