Add quit confirmation to main menu

Implement a quit confirmation dialog that appears when the QUIT button is selected on the main menu. The dialog displays 'EXIT GAME?' with YES/NO options. YES sends a coAction quit request to the plugin, which hides the overlay, disconnects any active server session, and sets RE::Main::quitGame to exit the game. NO returns to the main menu. Includes full keyboard (arrow keys, Enter, Escape), gamepad (dpad, A/B buttons), and mouse support. Default selection is NO.
This commit is contained in:
2026-07-07 22:33:33 +12:00
parent 549c117818
commit 2a3e614a47
7 changed files with 379 additions and 7 deletions
+190 -6
View File
@@ -165,10 +165,13 @@
var menu = document.getElementById("menu");
var settingsPanel = document.getElementById("settingsPanel");
var browserPanel = document.getElementById("browserPanel");
var quitConfirmPanel = document.getElementById("quitConfirmPanel");
var quitConfirmOptions = document.getElementById("quitConfirmOptions");
var mainPromptBar = document.getElementById("mainPromptBar");
var settingsPromptBar = document.getElementById("settingsPromptBar");
if (!menu || !settingsPanel || !browserPanel || !window.CO_Settings || !window.CO_Browser) {
if (!menu || !settingsPanel || !browserPanel || !quitConfirmPanel || !quitConfirmOptions ||
!window.CO_Settings || !window.CO_Browser) {
return;
}
@@ -176,12 +179,17 @@
var browserController = null;
var items = Array.prototype.slice.call(menu.querySelectorAll(".menu-item"));
var quitConfirmItems = Array.prototype.slice.call(
quitConfirmOptions.querySelectorAll(".quit-confirm__option")
);
var MULTIPLAYER_INDEX = 0;
var SETTINGS_INDEX = 2;
var QUIT_INDEX = 4;
// viewMode: "menu" | "settings" | "browser"
// viewMode: "menu" | "settings" | "browser" | "quitConfirm"
var viewMode = "menu";
var selectedIndex = -1;
var quitConfirmSelectedIndex = 0;
var PANEL_TRANSITION_MS = 220;
var settingsHideFallbackId = 0;
var settingsHideOnTransitionEnd = null;
@@ -243,11 +251,11 @@
enterSettingsMode();
} else if (action === "multiplayer") {
enterBrowserMode();
} else if (action === "quit") {
enterQuitConfirmMode();
} else {
window.CommonwealthOnlineUI.onOpenView(action);
if (action !== "quit") {
setSelected(-1);
}
setSelected(-1);
}
}
@@ -263,6 +271,11 @@
return;
}
exitBrowserMode();
} else if (viewMode === "quitConfirm") {
if (index === QUIT_INDEX) {
return;
}
exitQuitConfirmMode();
}
if (item.classList.contains("is-disabled")) {
return;
@@ -472,6 +485,129 @@
exitBrowserMode(true);
};
function setQuitConfirmSelected(index) {
if (index >= quitConfirmItems.length) {
return;
}
quitConfirmSelectedIndex = index < 0 ? 0 : index;
quitConfirmItems.forEach(function (item, i) {
var isSelected = i === quitConfirmSelectedIndex;
item.classList.toggle("is-selected", isSelected);
item.setAttribute("aria-selected", isSelected ? "true" : "false");
item.tabIndex = isSelected ? 0 : -1;
});
}
function moveQuitConfirmSelection(delta) {
var next = quitConfirmSelectedIndex + delta;
if (next < 0) {
next = quitConfirmItems.length - 1;
} else if (next >= quitConfirmItems.length) {
next = 0;
}
setQuitConfirmSelected(next);
}
function showQuitConfirmPanelAnimated() {
if (mainScreen) {
mainScreen.classList.add("is-quit-confirm-mode");
}
quitConfirmPanel.hidden = false;
quitConfirmPanel.classList.remove("is-open");
void quitConfirmPanel.offsetWidth;
quitConfirmPanel.classList.add("is-open");
}
function hideQuitConfirmPanelAnimated(done) {
if (quitConfirmPanel.hidden) {
if (done) {
done();
}
return;
}
quitConfirmPanel.classList.remove("is-open");
window.setTimeout(function finishHide() {
if (viewMode === "quitConfirm" || quitConfirmPanel.classList.contains("is-open")) {
if (done) {
done();
}
return;
}
if (mainScreen) {
mainScreen.classList.remove("is-quit-confirm-mode");
}
quitConfirmPanel.hidden = true;
if (done) {
done();
}
}, PANEL_TRANSITION_MS + 80);
}
function enterQuitConfirmMode() {
if (viewMode === "quitConfirm") {
return;
}
if (viewMode === "settings") {
exitSettingsMode();
} else if (viewMode === "browser") {
exitBrowserMode();
}
viewMode = "quitConfirm";
console.log("[CO Main Menu] Entering quit confirmation");
setSelected(QUIT_INDEX);
setQuitConfirmSelected(1);
showQuitConfirmPanelAnimated();
}
function exitQuitConfirmMode() {
if (viewMode !== "quitConfirm") {
return;
}
viewMode = "menu";
console.log("[CO Main Menu] Exiting quit confirmation");
hideQuitConfirmPanelAnimated(function () {
if (viewMode === "menu") {
setSelected(QUIT_INDEX);
}
});
}
function confirmQuit() {
console.log("[CO Main Menu] Quit confirmed");
sendCoAction({ action: "quit" });
}
function activateQuitConfirmSelected() {
var item = quitConfirmItems[quitConfirmSelectedIndex];
if (!item) {
return;
}
var choice = item.getAttribute("data-choice");
if (choice === "yes") {
confirmQuit();
return;
}
exitQuitConfirmMode();
}
quitConfirmItems.forEach(function (item, index) {
item.addEventListener("click", function () {
setQuitConfirmSelected(index);
activateQuitConfirmSelected();
});
});
window.onMainMenuShown = function () {
if (mainScreen) {
mainScreen.classList.remove("is-session-loading");
@@ -489,11 +625,14 @@
settingsController.hide();
settingsPanel.classList.remove("is-open");
settingsPanel.hidden = true;
} else if (viewMode === "quitConfirm") {
quitConfirmPanel.classList.remove("is-open");
quitConfirmPanel.hidden = true;
}
viewMode = "menu";
if (mainScreen) {
mainScreen.classList.remove("is-browser-mode", "is-settings-mode");
mainScreen.classList.remove("is-browser-mode", "is-settings-mode", "is-quit-confirm-mode");
mainScreen.classList.add("is-session-loading");
}
if (mainPromptBar) {
@@ -580,6 +719,30 @@
// ── Keyboard navigation ────────────────────────────────────────
function handleQuitConfirmKeydown(event) {
switch (event.key) {
case "ArrowUp":
event.preventDefault();
moveQuitConfirmSelection(-1);
break;
case "ArrowDown":
event.preventDefault();
moveQuitConfirmSelection(1);
break;
case "Enter":
case " ":
event.preventDefault();
activateQuitConfirmSelected();
break;
case "Escape":
event.preventDefault();
exitQuitConfirmMode();
break;
default:
break;
}
}
function handleMenuKeydown(event) {
switch (event.key) {
case "ArrowUp":
@@ -614,6 +777,10 @@
browserController.handleKeydown(event);
return;
}
if (viewMode === "quitConfirm") {
handleQuitConfirmKeydown(event);
return;
}
handleMenuKeydown(event);
});
@@ -626,6 +793,23 @@
if (viewMode === "settings") {
return;
}
if (viewMode === "quitConfirm") {
switch (button) {
case "dpad_up":
moveQuitConfirmSelection(-1);
break;
case "dpad_down":
moveQuitConfirmSelection(1);
break;
case "a":
activateQuitConfirmSelected();
break;
case "b":
exitQuitConfirmMode();
break;
}
return;
}
switch (button) {
case "dpad_up":
moveSelection(-1);