Deno desktop stuff

This commit is contained in:
2026-09-26 13:25:34 -03:00
parent b68fe3a19f
commit 86d5c39eab
10 changed files with 457 additions and 170 deletions
+46 -9
View File
@@ -20,8 +20,8 @@ const TITLE_SCALE = 8;
// for the player when it fails
export type JoinServer = (address: ServerAddress, name: string, status: (text: string) => void) => Promise<void>;
// the first thing players see, like minecraft's title and multiplayer screens rolled into one:
// a name, a server, and a button to join it
// the first thing players see, like minecraft's title and multiplayer screens rolled into one: a name, a server,
// and a button to join it. in the desktop app there's also singleplayer, which joins the app's own server
export class TitleScreen extends GuiScreen {
#join_server: JoinServer;
@@ -33,6 +33,10 @@ export class TitleScreen extends GuiScreen {
);
server = new EditBox(WIDTH, ROW_HEIGHT, new TextInput(initial_server(), 256, (char) => char !== " "), "host:port");
join_button = new Button("Join server", WIDTH, ROW_HEIGHT, () => this.#join());
// the page was served by a game server of its own to play on alone, see desktop/main.ts
singleplayer_button = has_singleplayer()
? new Button("Singleplayer", WIDTH, ROW_HEIGHT, () => this.#join_singleplayer())
: undefined;
credits_button = new Button("Credits", WIDTH, ROW_HEIGHT, () => this.#open_credits());
// open over the title screen, it gets the input while it's there
#credits: CreditsScreen | undefined;
@@ -75,6 +79,7 @@ export class TitleScreen extends GuiScreen {
if (InputManager.is_key_pressed("Enter")) {
this.#join();
}
this.singleplayer_button?.handle_input();
this.join_button.handle_input();
this.credits_button.handle_input();
}
@@ -92,6 +97,7 @@ export class TitleScreen extends GuiScreen {
this.#label("Name", this.name);
this.#label("Server", this.server);
this.name.render();
this.singleplayer_button?.render();
this.server.render();
this.join_button.render();
this.credits_button.render();
@@ -121,12 +127,18 @@ export class TitleScreen extends GuiScreen {
#layout() {
const x = (canvas.width - WIDTH) / 2;
const total = 2 * (LABEL_GAP + ROW_HEIGHT + GAP) + 2 * ROW_HEIGHT + GAP;
const singleplayer_height = this.singleplayer_button ? ROW_HEIGHT + GAP : 0;
const total = 2 * (LABEL_GAP + ROW_HEIGHT + GAP) + singleplayer_height + 2 * ROW_HEIGHT + GAP;
let y = (canvas.height - total) / 2 + 40;
for (const field of [this.name, this.server]) {
field.x = x;
field.y = y + LABEL_GAP;
y += LABEL_GAP + ROW_HEIGHT + GAP;
if (field === this.name && this.singleplayer_button) {
this.singleplayer_button.x = x;
this.singleplayer_button.y = y;
y += singleplayer_height;
}
}
this.join_button.x = x;
this.join_button.y = y;
@@ -151,10 +163,21 @@ export class TitleScreen extends GuiScreen {
return;
}
remember(LAST_SERVER_KEY, this.server.value.trim());
remember(LAST_NAME_KEY, this.name.value);
await this.#connect(address);
}
// the server that served this page
async #join_singleplayer() {
if (this.#joining) {
return;
}
await this.#connect(server_address(location.host), "Starting singleplayer...");
}
async #connect(address: ServerAddress, status = `Connecting to ${address.base.host}...`) {
remember(LAST_NAME_KEY, this.name.value);
this.#set_joining(true);
this.status = `Connecting to ${address.base.host}...`;
this.status = status;
this.status_is_error = false;
try {
await this.#join_server(address, this.name.value, (text) => this.status = text);
@@ -167,6 +190,9 @@ export class TitleScreen extends GuiScreen {
#set_joining(joining: boolean) {
this.#joining = joining;
this.name.active = this.server.active = this.join_button.active = !joining;
if (this.singleplayer_button) {
this.singleplayer_button.active = !joining;
}
}
#show_error(message: string) {
@@ -202,8 +228,19 @@ function remember(key: string, value: string) {
}
}
// a server's mods can't be unloaded, so going back to the title screen starts the page over. without ?server=
// or ?name=, so the fields show what was used last
export function back_to_title() {
location.href = location.pathname;
function has_singleplayer() {
return new URL(location.href).searchParams.has("singleplayer");
}
// a server's mods can't be unloaded, so going back to the title screen starts the page over. without ?server=
// or ?name=, so the fields show what was used last. the desktop app keeps its ?singleplayer and ?server=, its saved
// fields don't last between launches and the page's own address isn't a server to join
export function back_to_title() {
const page = new URL(location.href);
const next = new URL(location.pathname, location.href);
if (has_singleplayer()) {
next.searchParams.set("server", page.searchParams.get("server") ?? "");
next.searchParams.set("singleplayer", "");
}
location.href = next.href;
}