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:
@@ -61,6 +61,23 @@
|
||||
<!-- Server browser view (content mounted by ../browser/browser.js) -->
|
||||
<section class="browser-view" id="browserPanel" aria-label="Server browser" hidden></section>
|
||||
|
||||
<!-- Quit confirmation -->
|
||||
<section class="quit-confirm" id="quitConfirmPanel" aria-label="Quit confirmation" hidden>
|
||||
<div class="quit-confirm__dialog" role="dialog" aria-modal="true" aria-labelledby="quitConfirmTitle">
|
||||
<h2 class="quit-confirm__title" id="quitConfirmTitle">EXIT GAME?</h2>
|
||||
<nav class="quit-confirm__options" id="quitConfirmOptions" aria-label="Quit confirmation options">
|
||||
<button type="button" class="menu-item quit-confirm__option" data-index="0" data-choice="yes">
|
||||
<span class="menu-item__label">YES</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
<button type="button" class="menu-item quit-confirm__option" data-index="1" data-choice="no">
|
||||
<span class="menu-item__label">NO</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Bottom controller prompts (main menu) -->
|
||||
<footer class="prompt-bar" id="mainPromptBar" aria-label="Controller prompts">
|
||||
<div class="prompt">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -441,3 +441,89 @@ body {
|
||||
),
|
||||
rgba(4, 8, 6, 0.32);
|
||||
}
|
||||
|
||||
/* Quit confirmation modal */
|
||||
|
||||
.quit-confirm {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 12;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: calc(24px * var(--co-ui-scale));
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition:
|
||||
opacity 220ms ease-out,
|
||||
visibility 0ms linear 220ms;
|
||||
}
|
||||
|
||||
.quit-confirm.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
opacity 220ms ease-out,
|
||||
visibility 0ms linear 0ms;
|
||||
}
|
||||
|
||||
.quit-confirm[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quit-confirm__dialog {
|
||||
width: min(calc(420px * var(--co-ui-scale)), calc(100vw - 80px * var(--co-ui-scale)));
|
||||
padding:
|
||||
calc(32px * var(--co-ui-scale))
|
||||
calc(36px * var(--co-ui-scale))
|
||||
calc(28px * var(--co-ui-scale));
|
||||
background: rgba(6, 12, 8, 0.9);
|
||||
border: 1px solid rgba(201, 168, 74, 0.35);
|
||||
box-shadow:
|
||||
inset 0 0 28px rgba(0, 0, 0, 0.35),
|
||||
0 8px 32px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.quit-confirm__title {
|
||||
margin-bottom: calc(24px * var(--co-ui-scale));
|
||||
font-size: calc(28px * var(--co-ui-scale));
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.22em;
|
||||
text-align: center;
|
||||
color: var(--co-amber);
|
||||
}
|
||||
|
||||
.quit-confirm__options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--co-menu-gap);
|
||||
}
|
||||
|
||||
.quit-confirm__option .menu-item__bar {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-screen.is-quit-confirm-mode .scene-overlay {
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(6, 10, 8, 0.92) 0%,
|
||||
rgba(6, 10, 8, 0.72) 35%,
|
||||
rgba(6, 10, 8, 0.55) 60%,
|
||||
rgba(6, 10, 8, 0.65) 100%
|
||||
),
|
||||
rgba(4, 8, 6, 0.42);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.quit-confirm {
|
||||
transition: opacity 120ms ease-out, visibility 0ms linear 120ms;
|
||||
}
|
||||
|
||||
.quit-confirm.is-open {
|
||||
transition: opacity 120ms ease-out, visibility 0ms linear 0ms;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user