Embed server browser as main menu modal

Refactors the browser UI into a reusable `CO_Browser.create()` module and adds embedded modal styling so MULTIPLAYER opens inside the main menu instead of swapping to a separate view. Updates main-menu JS/CSS to host, animate, and route input for the browser panel while keeping nav and prompts in sync. On the C++ side, PrismaUI now drives browser show/hide and event dispatch through the main menu view, and `ServerBrowserBridge::SetView()` was added so bridge events target the active host view.
This commit is contained in:
2026-07-07 17:35:15 +12:00
parent 2961eb6512
commit 9447bc308f
12 changed files with 1514 additions and 982 deletions
+220 -31
View File
@@ -17,34 +17,18 @@
}
};
// ── Pointer mode API for C++ invocation ─────────────────────────
window.CO_App = {
setPointerMode: function (mode) {
console.log("[CO Main Menu] Pointer mode:", mode);
}
};
// ── Pointer mode API for C++ invocation (patched after browser mount) ──
window.CO_App = {};
// ── Controller input handler ─────────────────────────────────────
window.handleControllerInput = function (button, phase) {
if (phase === "down") {
switch (button) {
case "dpad_up":
moveSelection(-1);
break;
case "dpad_down":
moveSelection(1);
break;
case "a":
activateSelected();
break;
case "b":
if (viewMode === "settings") {
exitSettingsMode();
}
break;
}
function sendCoAction(payload) {
var msg = JSON.stringify(payload);
if (typeof window.coAction === "function") {
window.coAction(msg);
return true;
}
};
console.warn("[CO Main Menu] coAction bridge unavailable:", payload);
return false;
}
// ── Compute UI scale ───────────────────────────────────────────
function updateScale() {
@@ -180,24 +164,29 @@
var mainScreen = document.getElementById("mainScreen");
var menu = document.getElementById("menu");
var settingsPanel = document.getElementById("settingsPanel");
var browserPanel = document.getElementById("browserPanel");
var mainPromptBar = document.getElementById("mainPromptBar");
var settingsPromptBar = document.getElementById("settingsPromptBar");
if (!menu || !settingsPanel || !window.CO_Settings) {
if (!menu || !settingsPanel || !browserPanel || !window.CO_Settings || !window.CO_Browser) {
return;
}
var settingsController = null;
var browserController = null;
var items = Array.prototype.slice.call(menu.querySelectorAll(".menu-item"));
var MULTIPLAYER_INDEX = 0;
var SETTINGS_INDEX = 2;
// viewMode: "menu" | "settings"
// viewMode: "menu" | "settings" | "browser"
var viewMode = "menu";
var selectedIndex = -1;
var SETTINGS_TRANSITION_MS = 220;
var PANEL_TRANSITION_MS = 220;
var settingsHideFallbackId = 0;
var settingsHideOnTransitionEnd = null;
var browserHideFallbackId = 0;
var browserHideOnTransitionEnd = null;
// ── Main menu selection ────────────────────────────────────────
@@ -252,6 +241,8 @@
if (action === "settings") {
enterSettingsMode();
} else if (action === "multiplayer") {
enterBrowserMode();
} else {
window.CommonwealthOnlineUI.onOpenView(action);
if (action !== "quit") {
@@ -267,6 +258,11 @@
return;
}
exitSettingsMode();
} else if (viewMode === "browser") {
if (index === MULTIPLAYER_INDEX) {
return;
}
exitBrowserMode();
}
if (item.classList.contains("is-disabled")) {
return;
@@ -342,10 +338,151 @@
};
settingsPanel.addEventListener("transitionend", settingsHideOnTransitionEnd);
settingsHideFallbackId = window.setTimeout(finishHide, SETTINGS_TRANSITION_MS + 80);
settingsHideFallbackId = window.setTimeout(finishHide, PANEL_TRANSITION_MS + 80);
}
function cancelBrowserPanelHide() {
if (browserHideFallbackId) {
window.clearTimeout(browserHideFallbackId);
browserHideFallbackId = 0;
}
if (browserHideOnTransitionEnd) {
browserPanel.removeEventListener("transitionend", browserHideOnTransitionEnd);
browserHideOnTransitionEnd = null;
}
}
function showBrowserPanelAnimated() {
cancelBrowserPanelHide();
if (mainScreen) {
mainScreen.classList.add("is-browser-mode");
}
browserPanel.hidden = false;
browserPanel.classList.remove("is-open");
void browserPanel.offsetWidth;
browserPanel.classList.add("is-open");
}
function hideBrowserPanelAnimated(done) {
if (browserPanel.hidden) {
if (done) {
done();
}
return;
}
cancelBrowserPanelHide();
browserPanel.classList.remove("is-open");
var completed = false;
function finishHide() {
if (completed) {
return;
}
completed = true;
cancelBrowserPanelHide();
if (viewMode === "browser" || browserPanel.classList.contains("is-open")) {
if (done) {
done();
}
return;
}
if (mainScreen) {
mainScreen.classList.remove("is-browser-mode");
}
browserPanel.hidden = true;
if (done) {
done();
}
}
browserHideOnTransitionEnd = function (event) {
if (event.target !== browserPanel || event.propertyName !== "opacity") {
return;
}
finishHide();
};
browserPanel.addEventListener("transitionend", browserHideOnTransitionEnd);
browserHideFallbackId = window.setTimeout(finishHide, PANEL_TRANSITION_MS + 80);
}
function enterBrowserMode(fromNative) {
if (viewMode === "browser") {
if (fromNative) {
browserController.onShown();
}
return;
}
if (viewMode === "settings") {
exitSettingsMode();
}
viewMode = "browser";
console.log("[CO Main Menu] Entering browser mode");
window.CommonwealthOnlineUI.onOpenView("multiplayer");
setSelected(MULTIPLAYER_INDEX);
showBrowserPanelAnimated();
if (mainPromptBar) {
mainPromptBar.hidden = true;
}
browserController.show();
browserController.onShown();
if (!fromNative) {
sendCoAction({ action: "openView", view: "multiplayer" });
}
}
function exitBrowserMode(fromNative) {
if (viewMode !== "browser") {
return;
}
viewMode = "menu";
console.log("[CO Main Menu] Exiting browser mode");
window.CommonwealthOnlineUI.onBack();
browserController.hide();
hideBrowserPanelAnimated(function () {
if (mainPromptBar) {
mainPromptBar.hidden = false;
}
if (viewMode === "menu") {
setSelected(MULTIPLAYER_INDEX);
}
});
if (!fromNative) {
sendCoAction({ action: "closeBrowser" });
}
}
window.onBrowserShown = function () {
enterBrowserMode(true);
};
window.closeBrowser = function () {
exitBrowserMode(true);
};
function enterSettingsMode() {
if (viewMode === "browser") {
cancelBrowserPanelHide();
browserController.hide();
browserPanel.classList.remove("is-open");
browserPanel.hidden = true;
if (mainScreen) {
mainScreen.classList.remove("is-browser-mode");
}
}
viewMode = "settings";
console.log("[CO Main Menu] Entering settings mode");
@@ -376,7 +513,9 @@
if (mainPromptBar) {
mainPromptBar.hidden = false;
}
setSelected(SETTINGS_INDEX);
if (viewMode === "menu") {
setSelected(SETTINGS_INDEX);
}
});
}
@@ -390,6 +529,22 @@
}
});
browserController = window.CO_Browser.create({
mount: browserPanel,
layout: "embedded",
exposeGlobals: true,
captureKeys: false,
onBack: function () {
exitBrowserMode();
}
});
window.CO_App.setPointerMode = function (mode) {
if (viewMode === "browser") {
browserController.setPointerMode(mode);
}
};
// ── Keyboard navigation ────────────────────────────────────────
function handleMenuKeydown(event) {
@@ -422,7 +577,41 @@
settingsController.handleKeydown(event);
return;
}
if (viewMode === "browser") {
browserController.handleKeydown(event);
return;
}
handleMenuKeydown(event);
});
window.handleControllerInput = function (button, phase) {
if (phase === "down") {
if (viewMode === "browser") {
browserController.handleControllerInput(button, phase);
return;
}
if (viewMode === "settings") {
return;
}
switch (button) {
case "dpad_up":
moveSelection(-1);
break;
case "dpad_down":
moveSelection(1);
break;
case "a":
activateSelected();
break;
case "b":
if (viewMode === "browser") {
exitBrowserMode();
} else if (viewMode === "settings") {
exitSettingsMode();
}
break;
}
}
};
})();