From 24b47814f8e1272b1de4b40e4bf78240945fd0f3 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Wed, 1 Oct 2025 13:13:43 -0400 Subject: [PATCH] emscripten: Another attempt at optionally having the canvas use whole window. Fixes #11949. --- include/SDL3/SDL_hints.h | 22 +++++ include/SDL3/SDL_video.h | 14 +++ src/video/emscripten/SDL_emscriptenevents.c | 37 ++++--- src/video/emscripten/SDL_emscriptenvideo.c | 102 ++++++++++++++++---- src/video/emscripten/SDL_emscriptenvideo.h | 2 + 5 files changed, 145 insertions(+), 32 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 20ff1abcc1..f83e9600da 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -753,6 +753,28 @@ extern "C" { */ #define SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT "SDL_EMSCRIPTEN_KEYBOARD_ELEMENT" +/** + * Dictate that newly-created windows will fill the whole browser window. + * + * The canvas element fills the entire document. Resize events will be + * generated as the browser window is resized, as that will adjust the canvas + * size as well. The canvas will cover anything else on the page, including + * any controls provided by Emscripten in its generated HTML file. Often times + * this is desirable for a browser-based game, but it means several things that + * we expect of an SDL window on other platforms might not work as expected, + * such as minimum window sizes and aspect ratios. + * + * This hint overrides SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN + * properties when creating an SDL window. + * + * This hint only applies to the emscripten platform. + * + * This hint should be set before creating a window. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_EMSCRIPTEN_FILL_DOCUMENT "SDL_EMSCRIPTEN_FILL_DOCUMENT" + /** * A variable that controls whether the on-screen keyboard should be shown * when text input is active. diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 5043153da6..e72fe18955 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -1353,6 +1353,15 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreatePopupWindow(SDL_Window *paren * * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING`: the id given to the * canvas element. This should start with a '#' sign + * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN`: true to make + * the canvas element fill the entire document. Resize events will be + * generated as the browser window is resized, as that will adjust the + * canvas size as well. The canvas will cover anything else on the page, + * including any controls provided by Emscripten in its generated HTML + * file. Often times this is desirable for a browser-based game, but it + * means several things that we expect of an SDL window on other platforms + * might not work as expected, such as minimum window sizes and aspect + * ratios. Default false. * - `SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: override the * binding element for keyboard inputs for this canvas. The variable can be * one of: @@ -1426,6 +1435,7 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_CreateWindowWithProperties(SDL_Prop #define SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER "SDL.window.create.win32.pixel_format_hwnd" #define SDL_PROP_WINDOW_CREATE_X11_WINDOW_NUMBER "SDL.window.create.x11.window" #define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.create.emscripten.canvas_id" +#define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN "SDL.window.create.emscripten.fill_document" #define SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.create.emscripten.keyboard_element" /** @@ -1595,6 +1605,9 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window) * * - `SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING`: the id the canvas element * will have + * - `SDL_PROP_WINDOW_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN`: true if the + * canvas is set to consume the entire browser window, bypassing some SDL + * window functionality. * - `SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING`: the keyboard * element that associates keyboard events to this window * @@ -1644,6 +1657,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window #define SDL_PROP_WINDOW_X11_SCREEN_NUMBER "SDL.window.x11.screen" #define SDL_PROP_WINDOW_X11_WINDOW_NUMBER "SDL.window.x11.window" #define SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING "SDL.window.emscripten.canvas_id" +#define SDL_PROP_WINDOW_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN "SDL.window.emscripten.fill_document" #define SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING "SDL.window.emscripten.keyboard_element" /** diff --git a/src/video/emscripten/SDL_emscriptenevents.c b/src/video/emscripten/SDL_emscriptenevents.c index 97a80d57e8..7e5a7e561b 100644 --- a/src/video/emscripten/SDL_emscriptenevents.c +++ b/src/video/emscripten/SDL_emscriptenevents.c @@ -505,24 +505,31 @@ static EM_BOOL Emscripten_HandleFullscreenChangeGlobal(int eventType, const Emsc static EM_BOOL Emscripten_HandleResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { SDL_WindowData *window_data = userData; - bool force = false; - - // update pixel ratio - if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { - if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) { - window_data->pixel_ratio = emscripten_get_device_pixel_ratio(); - force = true; - } - } if (!(window_data->window->flags & SDL_WINDOW_FULLSCREEN)) { - // this will only work if the canvas size is set through css - if (window_data->window->flags & SDL_WINDOW_RESIZABLE) { - double w = window_data->window->w; - double h = window_data->window->h; + bool force = false; - if (window_data->external_size) { - emscripten_get_element_css_size(window_data->canvas_id, &w, &h); + // update pixel ratio + if (window_data->window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { + if (window_data->pixel_ratio != emscripten_get_device_pixel_ratio()) { + window_data->pixel_ratio = emscripten_get_device_pixel_ratio(); + force = true; + } + } + + if (window_data->fill_document || (window_data->window->flags & SDL_WINDOW_RESIZABLE)) { + double w, h; + if (window_data->fill_document) { + w = (double) uiEvent->windowInnerWidth; + h = (double) uiEvent->windowInnerHeight; + } else { + SDL_assert(window_data->window->flags & SDL_WINDOW_RESIZABLE); + w = window_data->window->w; + h = window_data->window->h; + // this will only work if the canvas size is set through css + if (window_data->external_size) { + emscripten_get_element_css_size(window_data->canvas_id, &w, &h); + } } emscripten_set_canvas_element_size(window_data->canvas_id, SDL_lroundf(w * window_data->pixel_ratio), SDL_lroundf(h * window_data->pixel_ratio)); diff --git a/src/video/emscripten/SDL_emscriptenvideo.c b/src/video/emscripten/SDL_emscriptenvideo.c index 8b0d4ff339..9f12f09b57 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.c +++ b/src/video/emscripten/SDL_emscriptenvideo.c @@ -478,6 +478,12 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, } wdata->keyboard_element = SDL_strdup(selector); + if (SDL_GetHint(SDL_HINT_EMSCRIPTEN_FILL_DOCUMENT)) { + wdata->fill_document = SDL_GetHintBoolean(SDL_HINT_EMSCRIPTEN_FILL_DOCUMENT, false); + } else { + wdata->fill_document = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN, false); + } + if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { wdata->pixel_ratio = emscripten_get_device_pixel_ratio(); } else { @@ -492,19 +498,56 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, emscripten_get_element_css_size(wdata->canvas_id, &css_w, &css_h); wdata->external_size = SDL_floor(css_w) != 1 || SDL_floor(css_h) != 1; - - if ((window->flags & SDL_WINDOW_RESIZABLE) && wdata->external_size) { - // external css has resized us - scaled_w = css_w * wdata->pixel_ratio; - scaled_h = css_h * wdata->pixel_ratio; - - SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(css_w), SDL_lroundf(css_h)); + if (wdata->external_size) { + wdata->fill_document = false; // can't be resizable if something else is controlling it. } - emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h)); - // if the size is not being controlled by css, we need to scale down for hidpi - if (!wdata->external_size) { + // fill_document takes up the entire page and resizes as the browser window resizes. + if (wdata->fill_document) { + const int w = MAIN_THREAD_EM_ASM_INT({ return window.innerWidth; }); + const int h = MAIN_THREAD_EM_ASM_INT({ return window.innerHeight; }); + + scaled_w = w * wdata->pixel_ratio; + scaled_h = h * wdata->pixel_ratio; + + MAIN_THREAD_EM_ASM({ + var canvas = document.querySelector(UTF8ToString($0)); + + // hide everything on the page that isn't the canvas. + var div = document.createElement('div'); + div.id = 'SDL3_fill_document_background_elements'; + + div.SDL3_canvas = canvas; + div.SDL3_canvas_parent = canvas.parentNode; + div.SDL3_canvas_nextsib = canvas.nextSibling; + + var children = Array.from(document.body.children); + for (var child of children) { + div.appendChild(child); + } + document.body.appendChild(div); + div.style.display = 'none'; + document.body.appendChild(canvas); + canvas.style.position = 'fixed'; + canvas.style.top = '0'; + canvas.style.left = '0'; + }, wdata->canvas_id); + + emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h)); + + // set_canvas_size unsets this if (wdata->pixel_ratio != 1.0f) { + emscripten_set_element_css_size(wdata->canvas_id, w, h); + } + + // force the event to trigger, so pixel ratio changes can be handled + window->w = window->h = 0; + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, SDL_lroundf(w), SDL_lroundf(h)); + } else { + emscripten_set_canvas_element_size(wdata->canvas_id, SDL_lroundf(scaled_w), SDL_lroundf(scaled_h)); + + // if the size is not being controlled by css, we need to scale down for hidpi + if (!wdata->external_size && (wdata->pixel_ratio != 1.0f)) { // scale canvas down emscripten_set_element_css_size(wdata->canvas_id, window->w, window->h); } @@ -521,16 +564,17 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, Emscripten_RegisterEventHandlers(wdata); - // disable the emscripten "fullscreen" button. + // Make the emscripten "fullscreen" button go through SDL. MAIN_THREAD_EM_ASM({ Module['requestFullscreen'] = function(lockPointer, resizeCanvas) { _requestFullscreenThroughSDL($0); }; }, window); - // Ensure canvas_id and keyboard_element are added to the window's properties + // Ensure various things are added to the window's properties SDL_SetStringProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_CANVAS_ID_STRING, wdata->canvas_id); SDL_SetStringProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_KEYBOARD_ELEMENT_STRING, wdata->keyboard_element); + SDL_SetBooleanProperty(window->props, SDL_PROP_WINDOW_EMSCRIPTEN_FILL_DOCUMENT_BOOLEAN, wdata->fill_document); // Window has been successfully created return true; @@ -538,10 +582,12 @@ static bool Emscripten_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, static void Emscripten_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) { - SDL_WindowData *data; - if (window->internal) { - data = window->internal; + SDL_WindowData *data = window->internal; + if (data->fill_document) { + return; // canvas size is being dictated by the browser window size, refuse request. + } + // update pixel ratio if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { data->pixel_ratio = emscripten_get_device_pixel_ratio(); @@ -578,6 +624,9 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) // We can't destroy the canvas, so resize it to zero instead emscripten_set_canvas_element_size(data->canvas_id, 0, 0); + if (data->pixel_ratio != 1.0f) { + emscripten_set_element_css_size(data->canvas_id, 1, 1); + } SDL_free(data->canvas_id); SDL_free(data->keyboard_element); @@ -586,8 +635,27 @@ static void Emscripten_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) window->internal = NULL; } - // just ignore clicks on the fullscreen button while there's no SDL window. - MAIN_THREAD_EM_ASM({ Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {}; }); + MAIN_THREAD_EM_ASM({ + // just ignore clicks on the fullscreen button while there's no SDL window. + Module['requestFullscreen'] = function(lockPointer, resizeCanvas) {}; + + // if we had previously hidden everything behind a fill_document window, put it back. + var div = document.getElementById('SDL3_fill_document_background_elements'); + if (div) { + if (div.SDL3_canvas_nextsib) { + div.SDL3_canvas_parent.insertBefore(div.SDL3_canvas, div.SDL3_canvas_nextsib); + } else { + div.SDL3_canvas_parent.appendChild(div.SDL3_canvas); + } + while (div.firstChild) { + document.body.insertBefore(div.firstChild, div); + } + div.SDL3_canvas.style.position = undefined; + div.SDL3_canvas.style.top = undefined; + div.SDL3_canvas.style.left = undefined; + div.remove(); + } + }); } static SDL_FullscreenResult Emscripten_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) diff --git a/src/video/emscripten/SDL_emscriptenvideo.h b/src/video/emscripten/SDL_emscriptenvideo.h index db4751ce88..4abe2cf5f7 100644 --- a/src/video/emscripten/SDL_emscriptenvideo.h +++ b/src/video/emscripten/SDL_emscriptenvideo.h @@ -38,6 +38,8 @@ struct SDL_WindowData char *canvas_id; char *keyboard_element; + bool fill_document; + float pixel_ratio; bool external_size;