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:
@@ -5,6 +5,8 @@
|
||||
<title>Commonwealth Online — Main Menu</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="../settings/settings.css">
|
||||
<link rel="stylesheet" href="../browser/styles.css">
|
||||
<link rel="stylesheet" href="../browser/browser-modal.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-screen" id="mainScreen">
|
||||
@@ -56,6 +58,9 @@
|
||||
<!-- Settings view (content mounted by ../settings/settings.js) -->
|
||||
<section class="settings-view" id="settingsPanel" aria-label="Settings" hidden></section>
|
||||
|
||||
<!-- Server browser view (content mounted by ../browser/browser.js) -->
|
||||
<section class="browser-view" id="browserPanel" aria-label="Server browser" hidden></section>
|
||||
|
||||
<!-- Bottom controller prompts (main menu) -->
|
||||
<footer class="prompt-bar" id="mainPromptBar" aria-label="Controller prompts">
|
||||
<div class="prompt">
|
||||
@@ -103,6 +108,19 @@
|
||||
|
||||
<script src="../settings/settings-data.js"></script>
|
||||
<script src="../settings/settings.js"></script>
|
||||
<script src="../browser/components/layout.js"></script>
|
||||
<script src="../browser/components/theme.js"></script>
|
||||
<script src="../browser/components/mock-data.js"></script>
|
||||
<script src="../browser/components/tabs.js"></script>
|
||||
<script src="../browser/components/filters.js"></script>
|
||||
<script src="../browser/components/server-list.js"></script>
|
||||
<script src="../browser/components/server-details.js"></script>
|
||||
<script src="../browser/components/controller-glyphs.js"></script>
|
||||
<script src="../browser/components/controller-icons.js"></script>
|
||||
<script src="../browser/components/controller-prompts.js"></script>
|
||||
<script src="../browser/components/direct-connect.js"></script>
|
||||
<script src="../browser/components/hover.js"></script>
|
||||
<script src="../browser/browser.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -406,3 +406,32 @@ body {
|
||||
),
|
||||
rgba(4, 8, 6, 0.32);
|
||||
}
|
||||
|
||||
/* Browser mode: keep main nav, dim background more */
|
||||
|
||||
.main-screen.is-browser-mode .left-panel {
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(8, 14, 10, 0.9) 0%,
|
||||
rgba(8, 14, 10, 0.78) 65%,
|
||||
rgba(8, 14, 10, 0.15) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.main-screen.is-browser-mode .menu-item__bar {
|
||||
width: calc(100% + 28px * var(--co-ui-scale));
|
||||
}
|
||||
|
||||
.main-screen.is-browser-mode .scene-overlay {
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(6, 10, 8, 0.88) 0%,
|
||||
rgba(6, 10, 8, 0.55) 24%,
|
||||
rgba(6, 10, 8, 0.4) 55%,
|
||||
rgba(6, 10, 8, 0.48) 100%
|
||||
),
|
||||
rgba(4, 8, 6, 0.32);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user